From a4b1bafeef8bdffca70d331d0c5416a99ab1d084 Mon Sep 17 00:00:00 2001 From: Samuel Wanjohi Date: Thu, 8 Jan 2026 22:07:43 +0300 Subject: [PATCH 01/14] Add support for dotnet Native DateTime types --- ODataCodeGenTools.sln | 4 +- azure-pipelines.yml | 12 +- .../ODataCliFileHandler.cs | 9 + .../PackageInstallerHelpers.cs | 2 +- .../CodeGeneration/V4CodeGenDescriptor.cs | 3 + .../FileHandling/IFileHandler.cs | 9 +- .../Templates/ODataT4CodeGenerator.cs | 34 +++- .../Templates/ODataT4CodeGenerator.ttinclude | 26 ++- .../ConnectedServiceFileHandler.cs | 74 +++++--- .../ODataConnectedService_VS2022Plus.csproj | 1 + .../source.extension.vsixmanifest | 12 +- .../ConnectedServiceFileHandlerTests.cs | 176 ++++++++++++++++++ .../ODataConnectedService.Tests.csproj | 1 + .../Templates/ODataT4CodeGeneratorTests.cs | 102 ++++++++++ 14 files changed, 424 insertions(+), 41 deletions(-) create mode 100644 test/ODataConnectedService.Tests/FileHandling/ConnectedServiceFileHandlerTests.cs diff --git a/ODataCodeGenTools.sln b/ODataCodeGenTools.sln index 0a433b62..dc349366 100644 --- a/ODataCodeGenTools.sln +++ b/ODataCodeGenTools.sln @@ -1,7 +1,7 @@  Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 16 -VisualStudioVersion = 16.0.29613.14 +# Visual Studio Version 18 +VisualStudioVersion = 18.3.11312.210 MinimumVisualStudioVersion = 10.0.40219.1 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ODataConnectedService", "src\ODataConnectedService\ODataConnectedService.csproj", "{A8BC5B8E-9AB7-4257-B8F1-E7C62169F9B5}" EndProject diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 911da61e..7772fb53 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -42,6 +42,12 @@ stages: - job: Build steps: + - task: UseDotNet@2 + displayName: 'Use .NET SDK 8.0.x' + inputs: + packageType: 'sdk' + version: '8.0.x' + - task: NuGetToolInstaller@0 inputs: versionSpec: '>=5.2.0' @@ -57,6 +63,7 @@ stages: solution: '$(sln)' platform: '$(buildPlatform)' configuration: '$(buildConfiguration)' + msbuildArgs: '/p:DeployExtension=false' - task: DotNetCoreCLI@2 displayName: 'Pack Microsoft.OData.Cli package' @@ -135,7 +142,7 @@ stages: Contents: '$(productFilesVs2022Plus)' TargetFolder: '$(Build.ArtifactStagingDirectory)\Product2022plus' OverWrite: true - + - task: PublishBuildArtifacts@1 displayName: 'Publish Artifact - VSIX' inputs: @@ -149,7 +156,7 @@ stages: PathtoPublish: '$(Build.ArtifactStagingDirectory)\VSIX2022Plus' ArtifactName: 'VSIX2022Plus' publishLocation: 'Container' - + - task: PublishBuildArtifacts@1 displayName: 'Publish Artifact - ODataCLINupkg' inputs: @@ -323,3 +330,4 @@ stages: PathtoPublish: '$(Build.ArtifactStagingDirectory)\SignedODataCLINupkg' ArtifactName: SignedODataCLINupkg publishLocation: 'Container' + \ No newline at end of file diff --git a/src/Microsoft.OData.Cli/ODataCliFileHandler.cs b/src/Microsoft.OData.Cli/ODataCliFileHandler.cs index cd9e1719..a98fa9ff 100644 --- a/src/Microsoft.OData.Cli/ODataCliFileHandler.cs +++ b/src/Microsoft.OData.Cli/ODataCliFileHandler.cs @@ -96,6 +96,15 @@ public Task EmitContainerPropertyAttributeAsync() return Task.FromResult(true); } + /// + /// True if the native date and time types can be emitted; otherwise, false. + /// + /// A bool indicating whether to emit native date and time types or not + public Task EmitNativeDateTimeTypesAsync() + { + return Task.FromResult(true); + } + /// /// Sets the CSDL file as an embedded resource /// diff --git a/src/Microsoft.OData.Cli/PackageInstallers/PackageInstallerHelpers.cs b/src/Microsoft.OData.Cli/PackageInstallers/PackageInstallerHelpers.cs index b73bf9b7..7bc08099 100644 --- a/src/Microsoft.OData.Cli/PackageInstallers/PackageInstallerHelpers.cs +++ b/src/Microsoft.OData.Cli/PackageInstallers/PackageInstallerHelpers.cs @@ -220,7 +220,7 @@ private async Task GetPackageLatestNugetVersionAsync( PackageSearchResource searchResource = await sourceRepository.GetResourceAsync(); string[] targetProjectFrameworks = new[] { projectTargetFramework }; - SearchFilter searchFilter = new SearchFilter(false) + SearchFilter searchFilter = new SearchFilter(true) // True to include prerelease packages { SupportedFrameworks = targetProjectFrameworks }; diff --git a/src/Microsoft.OData.CodeGen/CodeGeneration/V4CodeGenDescriptor.cs b/src/Microsoft.OData.CodeGen/CodeGeneration/V4CodeGenDescriptor.cs index bc32ef2c..0ab5506a 100644 --- a/src/Microsoft.OData.CodeGen/CodeGeneration/V4CodeGenDescriptor.cs +++ b/src/Microsoft.OData.CodeGen/CodeGeneration/V4CodeGenDescriptor.cs @@ -196,6 +196,9 @@ private async Task AddGeneratedCodeAsync(string metadata, string outputDirectory await FileHandler.SetFileAsEmbeddedResourceAsync(csdlFileName); t4CodeGenerator.EmitContainerPropertyAttribute = await FileHandler.EmitContainerPropertyAttributeAsync(); + // Determine whether to emit native DateOnly and TimeOnly types + t4CodeGenerator.EmitNativeDateTimeTypes = await FileHandler.EmitNativeDateTimeTypesAsync(); + t4CodeGenerator.MetadataFilePath = metadataFile; t4CodeGenerator.MetadataFileRelativePath = csdlFileName; diff --git a/src/Microsoft.OData.CodeGen/FileHandling/IFileHandler.cs b/src/Microsoft.OData.CodeGen/FileHandling/IFileHandler.cs index 5f313079..8dbe9b19 100644 --- a/src/Microsoft.OData.CodeGen/FileHandling/IFileHandler.cs +++ b/src/Microsoft.OData.CodeGen/FileHandling/IFileHandler.cs @@ -31,7 +31,14 @@ public interface IFileHandler /// /// Emits container property attribute /// - /// + /// >A task that represents the asynchronous operation. true if container property can be emitted; otherwise false Task EmitContainerPropertyAttributeAsync(); + + /// + /// Emits dotnet native date and time types (DateOnly and TimeOnly) to the target environment asynchronously. + /// + /// A task that represents the asynchronous operation. true if the native + /// date and time types can be emitted; otherwise, false. + Task EmitNativeDateTimeTypesAsync(); } } diff --git a/src/Microsoft.OData.CodeGen/Templates/ODataT4CodeGenerator.cs b/src/Microsoft.OData.CodeGen/Templates/ODataT4CodeGenerator.cs index 8f70adb8..5de1263c 100644 --- a/src/Microsoft.OData.CodeGen/Templates/ODataT4CodeGenerator.cs +++ b/src/Microsoft.OData.CodeGen/Templates/ODataT4CodeGenerator.cs @@ -209,7 +209,8 @@ public virtual async Task TransformTextAsync() ExcludedOperationImports = this.ExcludedOperationImports, ExcludedBoundOperations = this.ExcludedBoundOperations, ExcludedSchemaTypes = this.ExcludedSchemaTypes, - EmitContainerPropertyAttribute = this.EmitContainerPropertyAttribute + EmitContainerPropertyAttribute = this.EmitContainerPropertyAttribute, + EmitNativeDateTimeTypes = this.EmitNativeDateTimeTypes }; } else @@ -250,7 +251,8 @@ public virtual async Task TransformTextAsync() ExcludedOperationImports = this.ExcludedOperationImports, ExcludedBoundOperations = this.ExcludedBoundOperations, ExcludedSchemaTypes = this.ExcludedSchemaTypes, - EmitContainerPropertyAttribute = this.EmitContainerPropertyAttribute + EmitContainerPropertyAttribute = this.EmitContainerPropertyAttribute, + EmitNativeDateTimeTypes = this.EmitNativeDateTimeTypes }; } @@ -568,6 +570,15 @@ public bool EmitContainerPropertyAttribute internal set; } +/// +/// true to emit .NET date/time types (DateOnly and TimeOnly) instead of Microsoft.OData.Edm.Date and Microsoft.OData.Edm.TimeOfDay, false otherwise +/// +public bool EmitNativeDateTimeTypes +{ + get; + internal set; +} + /// /// Generate code targeting a specific .Net Framework language. /// @@ -1333,6 +1344,17 @@ public bool EmitContainerPropertyAttribute set; } + /// + /// true to use native .NET date/time types (DateOnly and TimeOnly) instead of + /// Microsoft.OData.Edm.Date and Microsoft.OData.Edm.TimeOfDay, false otherwise. + /// Set to true when targeting .NET 10.0 or later. + /// + public bool EmitNativeDateTimeTypes + { + get; + set; + } + /// /// true if this EntityContainer need to set the UrlConvention to KeyAsSegment, false otherwise. /// @@ -4298,10 +4320,10 @@ public ODataClientCSharpTemplate(CodeGenerationContext context) internal override string GeometryMultiPolygonTypeName { get { return "global::Microsoft.Spatial.GeometryMultiPolygon"; } } internal override string GeometryMultiLineStringTypeName { get { return "global::Microsoft.Spatial.GeometryMultiLineString"; } } internal override string GeometryMultiPointTypeName { get { return "global::Microsoft.Spatial.GeometryMultiPoint"; } } - internal override string DateTypeName { get { return "global::Microsoft.OData.Edm.Date"; } } + internal override string DateTypeName { get { return this.context.EmitNativeDateTimeTypes ? "global::System.DateOnly" : "global::Microsoft.OData.Edm.Date"; } } internal override string DateTimeOffsetTypeName { get { return "global::System.DateTimeOffset"; } } internal override string DurationTypeName { get { return "global::System.TimeSpan"; } } - internal override string TimeOfDayTypeName { get { return "global::Microsoft.OData.Edm.TimeOfDay"; } } + internal override string TimeOfDayTypeName { get { return this.context.EmitNativeDateTimeTypes ? "global::System.TimeOnly" : "global::Microsoft.OData.Edm.TimeOfDay"; } } internal override string XmlConvertClassName { get { return "global::System.Xml.XmlConvert"; } } internal override string EnumTypeName { get { return "global::System.Enum"; } } internal override string DictionaryInterfaceName { get { return "global::System.Collections.Generic.IDictionary<{0}, {1}>"; } } @@ -6441,10 +6463,10 @@ public ODataClientVBTemplate(CodeGenerationContext context) internal override string GeometryMultiPolygonTypeName { get { return "Global.Microsoft.Spatial.GeometryMultiPolygon"; } } internal override string GeometryMultiLineStringTypeName { get { return "Global.Microsoft.Spatial.GeometryMultiLineString"; } } internal override string GeometryMultiPointTypeName { get { return "Global.Microsoft.Spatial.GeometryMultiPoint"; } } - internal override string DateTypeName { get { return "Global.Microsoft.OData.Edm.Date"; } } + internal override string DateTypeName { get { return this.context.EmitNativeDateTimeTypes ? "Global.System.DateOnly" : "Global.Microsoft.OData.Edm.Date"; } } internal override string DateTimeOffsetTypeName { get { return "Global.System.DateTimeOffset"; } } internal override string DurationTypeName { get { return "Global.System.TimeSpan"; } } - internal override string TimeOfDayTypeName { get { return "Global.Microsoft.OData.Edm.TimeOfDay"; } } + internal override string TimeOfDayTypeName { get { return this.context.EmitNativeDateTimeTypes ? "Global.System.TimeOnly" : "Global.Microsoft.OData.Edm.TimeOfDay"; } } internal override string XmlConvertClassName { get { return "Global.System.Xml.XmlConvert"; } } internal override string EnumTypeName { get { return "Global.System.Enum"; } } internal override string DictionaryInterfaceName { get { return "Global.System.Collections.Generic.IDictionary(Of {0}, {1})"; } } diff --git a/src/Microsoft.OData.CodeGen/Templates/ODataT4CodeGenerator.ttinclude b/src/Microsoft.OData.CodeGen/Templates/ODataT4CodeGenerator.ttinclude index f50826fe..309b1743 100644 --- a/src/Microsoft.OData.CodeGen/Templates/ODataT4CodeGenerator.ttinclude +++ b/src/Microsoft.OData.CodeGen/Templates/ODataT4CodeGenerator.ttinclude @@ -66,7 +66,8 @@ public virtual async Task TransformTextAsync() ExcludedOperationImports = this.ExcludedOperationImports, ExcludedBoundOperations = this.ExcludedBoundOperations, ExcludedSchemaTypes = this.ExcludedSchemaTypes, - EmitContainerPropertyAttribute = this.EmitContainerPropertyAttribute + EmitContainerPropertyAttribute = this.EmitContainerPropertyAttribute, + EmitNativeDateTimeTypes = this.EmitNativeDateTimeTypes }; } else @@ -107,7 +108,8 @@ public virtual async Task TransformTextAsync() ExcludedOperationImports = this.ExcludedOperationImports, ExcludedBoundOperations = this.ExcludedBoundOperations, ExcludedSchemaTypes = this.ExcludedSchemaTypes, - EmitContainerPropertyAttribute = this.EmitContainerPropertyAttribute + EmitContainerPropertyAttribute = this.EmitContainerPropertyAttribute, + EmitNativeDateTimeTypes = this.EmitNativeDateTimeTypes }; } @@ -425,6 +427,15 @@ public bool EmitContainerPropertyAttribute internal set; } +/// +/// true to emit .NET date/time types (DateOnly and TimeOnly) instead of Microsoft.OData.Edm.Date and Microsoft.OData.Edm.TimeOfDay, false otherwise +/// +public bool EmitNativeDateTimeTypes +{ + get; + internal set; +} + /// /// Generate code targeting a specific .Net Framework language. /// @@ -1190,6 +1201,17 @@ public class CodeGenerationContext set; } + /// + /// true to use native .NET date/time types (DateOnly and TimeOnly) instead of + /// Microsoft.OData.Edm.Date and Microsoft.OData.Edm.TimeOfDay, false otherwise. + /// Set to true when targeting .NET 10.0 or later. + /// + public bool EmitNativeDateTimeTypes + { + get; + set; + } + /// /// true if this EntityContainer need to set the UrlConvention to KeyAsSegment, false otherwise. /// diff --git a/src/ODataConnectedService.Shared/ConnectedServiceFileHandler.cs b/src/ODataConnectedService.Shared/ConnectedServiceFileHandler.cs index d81b1756..8ca4595e 100644 --- a/src/ODataConnectedService.Shared/ConnectedServiceFileHandler.cs +++ b/src/ODataConnectedService.Shared/ConnectedServiceFileHandler.cs @@ -9,10 +9,10 @@ using System.Threading.Tasks; using EnvDTE; using Microsoft.OData.CodeGen.FileHandling; +using Microsoft.OData.ConnectedService.Threading; using Microsoft.VisualStudio.ConnectedServices; using Microsoft.VisualStudio.Shell; using VSLangProj; -using Microsoft.OData.ConnectedService.Threading; using Task = System.Threading.Tasks.Task; namespace Microsoft.OData.ConnectedService @@ -25,6 +25,10 @@ public class ConnectedServiceFileHandler : IFileHandler private ConnectedServiceHandlerContext Context; private readonly IThreadHelper threadHelper; + // Cache the OData Client version to avoid multiple project references enumeration + private Version odataClientVersion = null; + private bool isOdataClientVersionCached = false; + public Project Project { get; private set; } /// @@ -70,7 +74,7 @@ await this.threadHelper.RunInUiThreadAsync(() => } #pragma warning restore VSTHRD010 // This invokes the code in the required main thread. return false; - }); + }).ConfigureAwait(false); } /// @@ -79,27 +83,55 @@ await this.threadHelper.RunInUiThreadAsync(() => /// /// A value of either true or false public Task EmitContainerPropertyAttributeAsync() - => threadHelper.RunInUiThreadAsync(() => + => this.CheckODataClientVersionAsync(version => version > Version.Parse("7.6.4.0")); + + /// + /// Determines asynchronously whether native date and time types are supported by the connected OData service. + /// + /// A task that represents the asynchronous operation. True if native date and time types are supported; otherwise, false. + public Task EmitNativeDateTimeTypesAsync() + => this.CheckODataClientVersionAsync(version => version >= Version.Parse("9.0.0") || version >= Version.Parse("9.0.0.0")); + + /// + /// Checks if the Microsoft.OData.Client reference meets a version condition. + /// + /// A predicate to evaluate against the OData Client version. + /// True if the reference exists and meets the version condition; otherwise false. + private Task CheckODataClientVersionAsync(Func versionPredicate) + { + return this.threadHelper.RunInUiThreadAsync(() => + { + if (this.isOdataClientVersionCached) + { + return this.odataClientVersion != null && versionPredicate(this.odataClientVersion); + } - { #pragma warning disable VSTHRD010 // Invoke single-threaded types on Main thread - if (this.Project.Object is VSProject vsProject) - { - foreach (Reference reference in vsProject.References) - { - if (reference.SourceProject == null) - { - // Assembly reference (For project reference, SourceProject != null) - if (reference.Name.Equals("Microsoft.OData.Client", StringComparison.Ordinal)) - { - return Version.Parse(reference.Version) > Version.Parse("7.6.4.0"); - } - } - } - } + if (this.Project.Object is VSProject vsProject) + { + foreach (Reference reference in vsProject.References) + { + if (reference.SourceProject == null && + reference.Name.Equals("Microsoft.OData.Client", StringComparison.Ordinal)) + { + var currentVersion = reference.Version; + if (currentVersion.Contains("-")) + { + currentVersion = currentVersion.Substring(0, currentVersion.IndexOf('-')); + } + + this.odataClientVersion = Version.Parse(currentVersion); + this.isOdataClientVersionCached = true; + return versionPredicate(this.odataClientVersion); + } + } + } #pragma warning restore VSTHRD010 // Invoke single-threaded types on Main thread - return false; - }); + this.odataClientVersion = null; + this.isOdataClientVersionCached = true; + return false; + }); + } } -} \ No newline at end of file +} diff --git a/src/ODataConnectedService_VS2022Plus/ODataConnectedService_VS2022Plus.csproj b/src/ODataConnectedService_VS2022Plus/ODataConnectedService_VS2022Plus.csproj index 3965d552..1878634f 100644 --- a/src/ODataConnectedService_VS2022Plus/ODataConnectedService_VS2022Plus.csproj +++ b/src/ODataConnectedService_VS2022Plus/ODataConnectedService_VS2022Plus.csproj @@ -2,6 +2,7 @@ + 17.0 $(MSBuildExtensionsPath)\Microsoft\VisualStudio\v$(VisualStudioVersion) Debug AnyCPU diff --git a/src/ODataConnectedService_VS2022Plus/source.extension.vsixmanifest b/src/ODataConnectedService_VS2022Plus/source.extension.vsixmanifest index 4d5e0bf5..1eb3aa42 100644 --- a/src/ODataConnectedService_VS2022Plus/source.extension.vsixmanifest +++ b/src/ODataConnectedService_VS2022Plus/source.extension.vsixmanifest @@ -10,22 +10,22 @@ OData Connected Service - + amd64 - + amd64 - + amd64 - + arm64 - + arm64 - + arm64 diff --git a/test/ODataConnectedService.Tests/FileHandling/ConnectedServiceFileHandlerTests.cs b/test/ODataConnectedService.Tests/FileHandling/ConnectedServiceFileHandlerTests.cs new file mode 100644 index 00000000..84500830 --- /dev/null +++ b/test/ODataConnectedService.Tests/FileHandling/ConnectedServiceFileHandlerTests.cs @@ -0,0 +1,176 @@ +//----------------------------------------------------------------------------- +// +// Copyright (c) .NET Foundation and Contributors. All rights reserved. +// See License.txt in the project root for license information. +// +//---------------------------------------------------------------------------- + +using System.Collections.Generic; +using System.Threading.Tasks; +using EnvDTE; +using Microsoft.OData.CodeGen.Models; +using Microsoft.OData.ConnectedService; +using Microsoft.OData.ConnectedService.Tests.TestHelpers; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Moq; +using ODataConnectedService.Tests.TestHelpers; +using VSLangProj; + +namespace ODataConnectedService.Tests.FileHandling +{ + [TestClass] + public class ConnectedServiceFileHandlerTests + { + [TestMethod] + public async Task EmitNativeDateTimeTypesAsync_ShouldReturnTrue_WhenODataClientVersionIsGreaterThanOrEqualTo9_0_0Async() + { + // Arrange + var project = CreateProjectWithODataClientVersion("9.0.0"); + var fileHandler = CreateFileHandler(project); + + // Act + var result = await fileHandler.EmitNativeDateTimeTypesAsync().ConfigureAwait(false); + + // Assert + Assert.IsTrue(result); + } + + [TestMethod] + public async Task EmitNativeDateTimeTypesAsync_ShouldReturnTrue_WhenODataClientVersionIsPrereleaseAsync() + { + // Arrange + var project = CreateProjectWithODataClientVersion("9.0.0-preview.3"); + var fileHandler = CreateFileHandler(project); + + // Act + var result = await fileHandler.EmitNativeDateTimeTypesAsync().ConfigureAwait(false); + + // Assert + Assert.IsTrue(result); + } + + [TestMethod] + public async Task EmitNativeDateTimeTypesAsync_ShouldReturnFalse_WhenODataClientVersionIsLessThan9_0_0_Async() + { + // Arrange + var project = CreateProjectWithODataClientVersion("8.0.0"); + var fileHandler = CreateFileHandler(project); + + // Act + var result = await fileHandler.EmitNativeDateTimeTypesAsync().ConfigureAwait(false); + + // Assert + Assert.IsFalse(result); + } + + [TestMethod] + public async Task EmitNativeDateTimeTypesAsync_ShouldReturnFalse_WhenODataClientReferenceNotFoundAsync() + { + // Arrange + var project = CreateProjectWithoutODataClient(); + var fileHandler = CreateFileHandler(project); + + // Act + var result = await fileHandler.EmitNativeDateTimeTypesAsync().ConfigureAwait(false); + + // Assert + Assert.IsFalse(result); + } + + [TestMethod] + public async Task CheckODataClientVersionAsync_ShouldCacheVersion_AndReuseOnSubsequentCallsAsync() + { + // Arrange + var referenceMock = new Mock(); + referenceMock.SetupGet(r => r.Name).Returns("Microsoft.OData.Client"); + referenceMock.SetupGet(r => r.Version).Returns("9.0.0.0"); + referenceMock.SetupGet(r => r.SourceProject).Returns((Project)null); + + var referencesMock = new Mock(); + var callCount = 0; + referencesMock.Setup(r => r.GetEnumerator()) + .Returns(() => + { + callCount++; + return new List { referenceMock.Object }.GetEnumerator(); + }); + + var vsProjectMock = new Mock(); + vsProjectMock.SetupGet(vsp => vsp.References).Returns(referencesMock.Object); + + var projectMock = new Mock(); + projectMock.SetupGet(p => p.Object).Returns(vsProjectMock.Object); + + var fileHandler = CreateFileHandler(projectMock.Object); + + // Act + var result1 = await fileHandler.EmitNativeDateTimeTypesAsync().ConfigureAwait(false); + var result2 = await fileHandler.EmitContainerPropertyAttributeAsync().ConfigureAwait(false); + + // Assert + Assert.IsTrue(result1); + Assert.IsTrue(result2); + Assert.AreEqual(1, callCount, "References should only be enumerated once due to caching"); + } + + private static Project CreateProjectWithODataClientVersion(string version) + { + var referenceMock = new Mock(); + referenceMock.SetupGet(r => r.Name).Returns("Microsoft.OData.Client"); + referenceMock.SetupGet(r => r.Version).Returns(version); + referenceMock.SetupGet(r => r.SourceProject).Returns((Project)null); + + var referencesMock = new Mock(); + referencesMock.Setup(r => r.GetEnumerator()) + .Returns(new List { referenceMock.Object }.GetEnumerator()); + + var vsProjectMock = new Mock(); + vsProjectMock.SetupGet(vsp => vsp.References).Returns(referencesMock.Object); + + var projectMock = new Mock(); + projectMock.SetupGet(p => p.Object).Returns(vsProjectMock.Object); + + return projectMock.Object; + } + + private static Project CreateProjectWithoutODataClient() + { + var referenceMock = new Mock(); + referenceMock.SetupGet(r => r.Name).Returns("System.Core"); + referenceMock.SetupGet(r => r.Version).Returns("4.0.0.0"); + referenceMock.SetupGet(r => r.SourceProject).Returns((Project)null); + + var referencesMock = new Mock(); + referencesMock.Setup(r => r.GetEnumerator()) + .Returns(new List { referenceMock.Object }.GetEnumerator()); + + var vsProjectMock = new Mock(); + vsProjectMock.SetupGet(vsp => vsp.References).Returns(referencesMock.Object); + + var projectMock = new Mock(); + projectMock.SetupGet(p => p.Object).Returns(vsProjectMock.Object); + + return projectMock.Object; + } + + private static ConnectedServiceFileHandler CreateFileHandler(Project project) + { + var serviceConfig = new ServiceConfigurationV4 { ServiceName = "TestService" }; + var serviceInstance = new ODataConnectedServiceInstance + { + ServiceConfig = serviceConfig, + Name = "TestService" + }; + + var handlerHelper = new TestConnectedServiceHandlerHelper + { + ServicesRootFolder = "ConnectedServices" + }; + + var context = new TestConnectedServiceHandlerContext(serviceInstance, handlerHelper); + var threadHelper = new TestThreadHelper(); + + return new ConnectedServiceFileHandler(context, project, threadHelper); + } + } +} diff --git a/test/ODataConnectedService.Tests/ODataConnectedService.Tests.csproj b/test/ODataConnectedService.Tests/ODataConnectedService.Tests.csproj index 184faf88..1fd79c09 100644 --- a/test/ODataConnectedService.Tests/ODataConnectedService.Tests.csproj +++ b/test/ODataConnectedService.Tests/ODataConnectedService.Tests.csproj @@ -70,6 +70,7 @@ + diff --git a/test/ODataConnectedService.Tests/Templates/ODataT4CodeGeneratorTests.cs b/test/ODataConnectedService.Tests/Templates/ODataT4CodeGeneratorTests.cs index fa7a745a..52e85214 100644 --- a/test/ODataConnectedService.Tests/Templates/ODataT4CodeGeneratorTests.cs +++ b/test/ODataConnectedService.Tests/Templates/ODataT4CodeGeneratorTests.cs @@ -978,5 +978,107 @@ public void GenerateDynamicPropertyContainerWithInheritance() Assert.IsTrue(normalizedGeneratedCode.IndexOf(otherContainerPropertyAttributeSnippet, StringComparison.Ordinal) == -1); } } + + [TestMethod] + public void SetEmitNativeDateTimeTypes_True_GenerateNativeDateTimeTypes() + { + var edmx = @" + + + + + + + + + + + + + + + + + + +"; + + var t4CodeGenerator = new ODataT4CodeGenerator + { + Edmx = edmx, + GetReferencedModelReaderFunc = null, + NamespacePrefix = null, + EnableNamingAlias = false, + IgnoreUnexpectedElementsAndAttributes = false, + GenerateMultipleFiles = false, + ExcludedSchemaTypes = null, + EmitNativeDateTimeTypes = true + }; + + // CSharp + t4CodeGenerator.TargetLanguage = ODataT4CodeGenerator.LanguageOption.CSharp; + var generatedCode = t4CodeGenerator.TransformText(); + + Assert.IsTrue(generatedCode.IndexOf("public virtual global::System.DateOnly OrderDate", StringComparison.Ordinal) > 0); + Assert.IsTrue(generatedCode.IndexOf("public virtual global::System.TimeOnly OrderTime", StringComparison.Ordinal) > 0); + + // VB + t4CodeGenerator.TargetLanguage = ODataT4CodeGenerator.LanguageOption.VB; + generatedCode = t4CodeGenerator.TransformText(); + + Assert.IsTrue(generatedCode.IndexOf("Public Overridable Property OrderDate() As Global.System.DateOnly", StringComparison.Ordinal) > 0); + Assert.IsTrue(generatedCode.IndexOf("Public Overridable Property OrderTime() As Global.System.TimeOnly", StringComparison.Ordinal) > 0); + } + + [TestMethod] + public void SetEmitNativeDateTimeTypes_False_GenerateODataEdmTypes() + { + var edmx = @" + + + + + + + + + + + + + + + + + + +"; + + var t4CodeGenerator = new ODataT4CodeGenerator + { + Edmx = edmx, + GetReferencedModelReaderFunc = null, + NamespacePrefix = null, + EnableNamingAlias = false, + IgnoreUnexpectedElementsAndAttributes = false, + GenerateMultipleFiles = false, + ExcludedSchemaTypes = null, + EmitNativeDateTimeTypes = false + }; + + // CSharp + t4CodeGenerator.TargetLanguage = ODataT4CodeGenerator.LanguageOption.CSharp; + var generatedCode = t4CodeGenerator.TransformText(); + + Assert.IsTrue(generatedCode.IndexOf("public virtual global::Microsoft.OData.Edm.Date OrderDate", StringComparison.Ordinal) > 0); + Assert.IsTrue(generatedCode.IndexOf("public virtual global::Microsoft.OData.Edm.TimeOfDay OrderTime", StringComparison.Ordinal) > 0); + + // VB + t4CodeGenerator.TargetLanguage = ODataT4CodeGenerator.LanguageOption.VB; + generatedCode = t4CodeGenerator.TransformText(); + + Assert.IsTrue(generatedCode.IndexOf("Public Overridable Property OrderDate() As Global.Microsoft.OData.Edm.Date", StringComparison.Ordinal) > 0); + Assert.IsTrue(generatedCode.IndexOf("Public Overridable Property OrderTime() As Global.Microsoft.OData.Edm.TimeOfDay", StringComparison.Ordinal) > 0); + } } } From 5c4d2537e6f835bd73bd6bee8854c4c4efdbb498 Mon Sep 17 00:00:00 2001 From: Samuel Wanjohi Date: Fri, 9 Jan 2026 06:55:01 +0300 Subject: [PATCH 02/14] Use IVsPackageInstaller2 --- .../ConnectedServicePackageInstaller.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/ODataConnectedService.Shared/ConnectedServicePackageInstaller.cs b/src/ODataConnectedService.Shared/ConnectedServicePackageInstaller.cs index 5cac7f2d..3b39f0c9 100644 --- a/src/ODataConnectedService.Shared/ConnectedServicePackageInstaller.cs +++ b/src/ODataConnectedService.Shared/ConnectedServicePackageInstaller.cs @@ -26,7 +26,7 @@ public class ConnectedServicePackageInstaller : IPackageInstaller public ConnectedServiceHandlerContext Context { get; private set; } public Project Project { get; private set; } public IMessageLogger MessageLogger { get; private set; } - public IVsPackageInstaller PackageInstaller { get; protected set; } + public IVsPackageInstaller2 PackageInstaller { get; protected set; } public IVsPackageInstallerServices PackageInstallerServices { get; protected set; } @@ -53,7 +53,7 @@ public void Init() if (componentModel != null) { this.PackageInstallerServices = componentModel.GetService(); - this.PackageInstaller = componentModel.GetService(); + this.PackageInstaller = componentModel.GetService(); } } @@ -70,7 +70,7 @@ public async Task CheckAndInstallNuGetPackageAsync(string packageSource, string { if (!PackageInstallerServices.IsPackageInstalled(this.Project, packageName)) { - PackageInstaller.InstallPackage(packageSource, this.Project, packageName, (string)null, false); ; + PackageInstaller.InstallLatestPackage(packageSource, this.Project, packageName, true, false); await (this.MessageLogger?.WriteMessageAsync(LogMessageCategory.Information, $"Nuget Package \"{packageName}\" for OData client was added.")).ConfigureAwait(false); } From 729676f224402611fc71018d4c1f239a29ecaafb Mon Sep 17 00:00:00 2001 From: Samuel Wanjohi Date: Fri, 9 Jan 2026 07:25:34 +0300 Subject: [PATCH 03/14] bump version --- .../source.extension.vsixmanifest | 78 +++++++++---------- 1 file changed, 39 insertions(+), 39 deletions(-) diff --git a/src/ODataConnectedService_VS2022Plus/source.extension.vsixmanifest b/src/ODataConnectedService_VS2022Plus/source.extension.vsixmanifest index 1eb3aa42..78a06121 100644 --- a/src/ODataConnectedService_VS2022Plus/source.extension.vsixmanifest +++ b/src/ODataConnectedService_VS2022Plus/source.extension.vsixmanifest @@ -1,42 +1,42 @@ - - - OData Connected Service 2022+ - OData Connected Service for V1-V4 - https://github.com/odata/ODataConnectedService - License.txt - Resources\ExtensionIcon.png - OData Connected Service - - - - amd64 - - - amd64 - - - amd64 - - - arm64 - - - arm64 - - - arm64 - - - - - - - - - - - - + + + OData Connected Service 2022+ + OData Connected Service for V1-V4 + https://github.com/odata/ODataConnectedService + License.txt + Resources\ExtensionIcon.png + OData Connected Service + + + + amd64 + + + amd64 + + + amd64 + + + arm64 + + + arm64 + + + arm64 + + + + + + + + + + + + From 6573d74363951b3c0873733af53919bdd691c0c9 Mon Sep 17 00:00:00 2001 From: Samuel Wanjohi Date: Fri, 9 Jan 2026 09:33:38 +0300 Subject: [PATCH 04/14] target vs18 --- .../ODataConnectedService_VS2022Plus.csproj | 1 - .../source.extension.vsixmanifest | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/src/ODataConnectedService_VS2022Plus/ODataConnectedService_VS2022Plus.csproj b/src/ODataConnectedService_VS2022Plus/ODataConnectedService_VS2022Plus.csproj index 1878634f..3965d552 100644 --- a/src/ODataConnectedService_VS2022Plus/ODataConnectedService_VS2022Plus.csproj +++ b/src/ODataConnectedService_VS2022Plus/ODataConnectedService_VS2022Plus.csproj @@ -2,7 +2,6 @@ - 17.0 $(MSBuildExtensionsPath)\Microsoft\VisualStudio\v$(VisualStudioVersion) Debug AnyCPU diff --git a/src/ODataConnectedService_VS2022Plus/source.extension.vsixmanifest b/src/ODataConnectedService_VS2022Plus/source.extension.vsixmanifest index 78a06121..ded9ca1d 100644 --- a/src/ODataConnectedService_VS2022Plus/source.extension.vsixmanifest +++ b/src/ODataConnectedService_VS2022Plus/source.extension.vsixmanifest @@ -33,7 +33,7 @@ - + From 85b553155d065205e7e193f8b1f304cb124b80e9 Mon Sep 17 00:00:00 2001 From: Samuel Wanjohi Date: Fri, 9 Jan 2026 11:08:48 +0300 Subject: [PATCH 05/14] change id --- .../source.extension.vsixmanifest | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ODataConnectedService_VS2022Plus/source.extension.vsixmanifest b/src/ODataConnectedService_VS2022Plus/source.extension.vsixmanifest index ded9ca1d..6347cbc3 100644 --- a/src/ODataConnectedService_VS2022Plus/source.extension.vsixmanifest +++ b/src/ODataConnectedService_VS2022Plus/source.extension.vsixmanifest @@ -1,7 +1,7 @@ - + OData Connected Service 2022+ OData Connected Service for V1-V4 https://github.com/odata/ODataConnectedService From 30d37146e55466c9253cdcd954d85c62cbcf12b9 Mon Sep 17 00:00:00 2001 From: Samuel Wanjohi Date: Fri, 9 Jan 2026 11:34:08 +0300 Subject: [PATCH 06/14] added VS2026Plus VSIX --- ODataCodeGenTools.sln | 16 +++ azure-pipelines.yml | 77 ++++++++++ .../source.extension.vsixmanifest | 78 +++++------ .../ODataConnectedService_VS2026Plus.csproj | 131 ++++++++++++++++++ .../OcsKey.snk | Bin 0 -> 596 bytes .../Properties/AssemblyInfo.cs | 34 +++++ .../source.extension.vsixmanifest | 43 ++++++ 7 files changed, 340 insertions(+), 39 deletions(-) create mode 100644 src/ODataConnectedService_VS2026Plus/ODataConnectedService_VS2026Plus.csproj create mode 100644 src/ODataConnectedService_VS2026Plus/OcsKey.snk create mode 100644 src/ODataConnectedService_VS2026Plus/Properties/AssemblyInfo.cs create mode 100644 src/ODataConnectedService_VS2026Plus/source.extension.vsixmanifest diff --git a/ODataCodeGenTools.sln b/ODataCodeGenTools.sln index dc349366..8e87b14b 100644 --- a/ODataCodeGenTools.sln +++ b/ODataCodeGenTools.sln @@ -26,6 +26,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{87FB2A7F-D EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{6500B077-1097-408B-B99A-6105EE8F4400}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ODataConnectedService_VS2026Plus", "src\ODataConnectedService_VS2026Plus\ODataConnectedService_VS2026Plus.csproj", "{A1558D4D-3675-4E76-9659-282E19772DEC}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -108,6 +110,18 @@ Global {FE62623D-B8AF-49E2-AEB2-4EF50419777A}.Release|arm64.Build.0 = Release|Any CPU {FE62623D-B8AF-49E2-AEB2-4EF50419777A}.Release|x86.ActiveCfg = Release|Any CPU {FE62623D-B8AF-49E2-AEB2-4EF50419777A}.Release|x86.Build.0 = Release|Any CPU + {A1558D4D-3675-4E76-9659-282E19772DEC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A1558D4D-3675-4E76-9659-282E19772DEC}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A1558D4D-3675-4E76-9659-282E19772DEC}.Debug|arm64.ActiveCfg = Debug|arm64 + {A1558D4D-3675-4E76-9659-282E19772DEC}.Debug|arm64.Build.0 = Debug|arm64 + {A1558D4D-3675-4E76-9659-282E19772DEC}.Debug|x86.ActiveCfg = Debug|x86 + {A1558D4D-3675-4E76-9659-282E19772DEC}.Debug|x86.Build.0 = Debug|x86 + {A1558D4D-3675-4E76-9659-282E19772DEC}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A1558D4D-3675-4E76-9659-282E19772DEC}.Release|Any CPU.Build.0 = Release|Any CPU + {A1558D4D-3675-4E76-9659-282E19772DEC}.Release|arm64.ActiveCfg = Release|arm64 + {A1558D4D-3675-4E76-9659-282E19772DEC}.Release|arm64.Build.0 = Release|arm64 + {A1558D4D-3675-4E76-9659-282E19772DEC}.Release|x86.ActiveCfg = Release|x86 + {A1558D4D-3675-4E76-9659-282E19772DEC}.Release|x86.Build.0 = Release|x86 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -120,6 +134,7 @@ Global {61E9E534-C672-4109-A6E1-9AA6F14B6C54} = {6500B077-1097-408B-B99A-6105EE8F4400} {15769DAB-F3B0-4E82-A125-CF55ACB04D07} = {6500B077-1097-408B-B99A-6105EE8F4400} {FE62623D-B8AF-49E2-AEB2-4EF50419777A} = {87FB2A7F-D758-4DBF-8B36-0007A3E156E9} + {A1558D4D-3675-4E76-9659-282E19772DEC} = {6500B077-1097-408B-B99A-6105EE8F4400} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {FD696714-8F52-4A00-93F4-43C843F184DB} @@ -127,6 +142,7 @@ Global GlobalSection(SharedMSBuildProjectFiles) = preSolution src\ODataConnectedService.Shared\ODataConnectedService.Shared.projitems*{15769dab-f3b0-4e82-a125-cf55acb04d07}*SharedItemsImports = 4 src\ODataConnectedService.Shared\ODataConnectedService.Shared.projitems*{61e9e534-c672-4109-a6e1-9aa6f14b6c54}*SharedItemsImports = 13 + src\ODataConnectedService.Shared\ODataConnectedService.Shared.projitems*{a1558d4d-3675-4e76-9659-282e19772dec}*SharedItemsImports = 4 src\ODataConnectedService.Shared\ODataConnectedService.Shared.projitems*{a8bc5b8e-9ab7-4257-b8f1-e7c62169f9b5}*SharedItemsImports = 4 EndGlobalSection EndGlobal diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 7772fb53..8b815426 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -17,18 +17,23 @@ variables: productFiles: 'Microsoft.OData.ConnectedService.*?(*.dll|*.config|*.pdb)' productBinPathVs2022Plus: '$(connectedServiceDir)\src\ODataConnectedService_VS2022Plus\bin\$(BuildConfiguration)' productFilesVs2022Plus: 'Microsoft.OData.ConnectedService.VS2022Plus.*?(*.dll|*.config|*.pdb)' + productBinPathVs2026Plus: '$(connectedServiceDir)\src\ODataConnectedService_VS2026Plus\bin\$(BuildConfiguration)' + productFilesVs2026Plus: 'Microsoft.OData.ConnectedService.VS2026Plus.*?(*.dll|*.config|*.pdb)' productFilesODataCLI: 'Microsoft.OData.Cli.*?(*.dll|*.config|*.pdb)' testBinPath: '$(connectedServiceDir)\test\ODataConnectedService.Tests\bin\$(BuildConfiguration)' testBinPathODataCLI: '$(connectedServiceDir)\test\Microsoft.OData.Cli.Tests\bin\$(BuildConfiguration)' vsixPath: '$(productBinPath)' vsixFile: 'Microsoft.OData.ConnectedService.vsix' vsixFileVs2022Plus: 'Microsoft.OData.ConnectedService.VS2022Plus.vsix' + vsixFileVs2026Plus: 'Microsoft.OData.ConnectedService.VS2026Plus.vsix' nupkgFile: 'Microsoft.OData.Cli.nupkg' signingFiles: 'Microsoft.OData.ConnectedService.dll' signingFilesVs2022Plus: 'Microsoft.OData.ConnectedService.VS2022Plus.dll' + signingFilesVs2026Plus: 'Microsoft.OData.ConnectedService.VS2026Plus.dll' signingFilesODataCLI: 'Microsoft.OData.Cli.dll' mainDll: 'Microsoft.OData.ConnectedService.dll' mainDllVs2022Plus: 'Microsoft.OData.ConnectedService.VS2022Plus.dll' + mainDllVs2026Plus: 'Microsoft.OData.ConnectedService.VS2026Plus.dll' mainDllODataCLI: 'Microsoft.OData.Cli.dll' testDll: 'ODataConnectedService.Tests.dll' testDllODataCLI: 'Microsoft.OData.Cli.Tests.dll' @@ -127,6 +132,14 @@ stages: TargetFolder: '$(Build.ArtifactStagingDirectory)\VSIX2022plus' OverWrite: true + - task: CopyFiles@2 + displayName: 'Copy Files - 2026plus VSIX to Artifacts Staging' + inputs: + SourceFolder: '$(productBinPathVs2026Plus)' + Contents: 'Microsoft.OData.ConnectedService.VS2026Plus.vsix' + TargetFolder: '$(Build.ArtifactStagingDirectory)\VSIX2026plus' + OverWrite: true + - task: CopyFiles@2 displayName: 'Copy Files - Stage Product' inputs: @@ -142,6 +155,14 @@ stages: Contents: '$(productFilesVs2022Plus)' TargetFolder: '$(Build.ArtifactStagingDirectory)\Product2022plus' OverWrite: true + + - task: CopyFiles@2 + displayName: 'Copy Files - Stage Product 2026plus' + inputs: + SourceFolder: '$(productBinPathVs2026Plus)' + Contents: '$(productFilesVs2026Plus)' + TargetFolder: '$(Build.ArtifactStagingDirectory)\Product2026plus' + OverWrite: true - task: PublishBuildArtifacts@1 displayName: 'Publish Artifact - VSIX' @@ -157,6 +178,13 @@ stages: ArtifactName: 'VSIX2022Plus' publishLocation: 'Container' + - task: PublishBuildArtifacts@1 + displayName: 'Publish Artifact - VSIX2026Plus' + inputs: + PathtoPublish: '$(Build.ArtifactStagingDirectory)\VSIX2026plus' + ArtifactName: 'VSIX2026Plus' + publishLocation: 'Container' + - task: PublishBuildArtifacts@1 displayName: 'Publish Artifact - ODataCLINupkg' inputs: @@ -268,6 +296,55 @@ stages: ArtifactName: 'SignedVSIX2022Plus' publishLocation: 'Container' +- stage: CodeSign3 + condition: and(succeeded('Build'), not(eq(variables['build.reason'], 'PullRequest'))) + jobs: + - deployment: CodeSign + displayName: Code Signing + pool: + vmImage: windows-latest + environment: Code Sign - Approvals + variables: + - group: Code Signing + strategy: + runOnce: + deploy: + steps: + - task: DotNetCoreCLI@2 + inputs: + command: custom + custom: tool + arguments: install --tool-path . --prerelease sign + displayName: Install SignTool tool + + # Sign the VSIX archive as well as files contained in the VSIX that are listed in config/filelist.txt + # For more info about the Sign CLI tool, visit: https://github.com/dotnet/sign + - task: AzureCLI@2 + inputs: + azureSubscription: 'Code Signing' + scriptType: pscore + scriptLocation: inlineScript + inlineScript: | + .\sign code azure-key-vault ` + "**/*.{dll,vsix,zip,nupkg}" ` + --base-directory "$(Pipeline.Workspace)/VSIX2026Plus" ` + --output "$(Build.ArtifactStagingDirectory)/SignedVSIX2026Plus" ` + --file-list "$(Pipeline.Workspace)/configs/filelist.txt" ` + --publisher-name "OData Labs" ` + --description "OData Connected Service 2026+" ` + --description-url "https://github.com/OData/ODataConnectedService" ` + --azure-key-vault-managed-identity ` + --azure-key-vault-certificate "$(SignKeyVaultCertificate)" ` + --azure-key-vault-url "$(SignKeyVaultUrl)" + displayName: Code signing - VSIX2026Plus + + # Publish the signed VSIX as an artifact so we can download it after the build + - task: PublishBuildArtifacts@1 + displayName: 'Publish Artifact: SignedVSIX2026Plus' + inputs: + PathtoPublish: '$(Build.ArtifactStagingDirectory)/SignedVSIX2026Plus' + ArtifactName: 'SignedVSIX2026Plus' + - stage: CodeSignODataCLI condition: and(succeeded('Build'), not(eq(variables['build.reason'], 'PullRequest'))) jobs: diff --git a/src/ODataConnectedService_VS2022Plus/source.extension.vsixmanifest b/src/ODataConnectedService_VS2022Plus/source.extension.vsixmanifest index 6347cbc3..4d5e0bf5 100644 --- a/src/ODataConnectedService_VS2022Plus/source.extension.vsixmanifest +++ b/src/ODataConnectedService_VS2022Plus/source.extension.vsixmanifest @@ -1,42 +1,42 @@ - - - OData Connected Service 2022+ - OData Connected Service for V1-V4 - https://github.com/odata/ODataConnectedService - License.txt - Resources\ExtensionIcon.png - OData Connected Service - - - - amd64 - - - amd64 - - - amd64 - - - arm64 - - - arm64 - - - arm64 - - - - - - - - - - - - + + + OData Connected Service 2022+ + OData Connected Service for V1-V4 + https://github.com/odata/ODataConnectedService + License.txt + Resources\ExtensionIcon.png + OData Connected Service + + + + amd64 + + + amd64 + + + amd64 + + + arm64 + + + arm64 + + + arm64 + + + + + + + + + + + + diff --git a/src/ODataConnectedService_VS2026Plus/ODataConnectedService_VS2026Plus.csproj b/src/ODataConnectedService_VS2026Plus/ODataConnectedService_VS2026Plus.csproj new file mode 100644 index 00000000..ee60e910 --- /dev/null +++ b/src/ODataConnectedService_VS2026Plus/ODataConnectedService_VS2026Plus.csproj @@ -0,0 +1,131 @@ + + + + 18.0 + $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) + + + + Debug + AnyCPU + 2.0 + {82b43b9b-a64c-4715-b499-d71e9ca2bd60};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + {A1558D4D-3675-4E76-9659-282E19772DEC} + Library + Properties + Microsoft.OData.ConnectedService.VS2026Plus + Microsoft.OData.ConnectedService.VS2026Plus + v4.7.2 + false + true + true + false + false + true + true + Program + $(DevEnvDir)devenv.exe + /rootsuffix Exp + + + true + full + false + bin\Debug\ + DEBUG;TRACE;VS2026PLUS + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE;VS2026PLUS + prompt + 4 + + + true + + + OcsKey.snk + + + + + + + + Designer + + + + + False + ..\..\external\Binaries\V3\Microsoft.Data.Edm.dll + + + False + ..\..\external\Binaries\V3\Microsoft.Data.OData.dll + + + False + ..\..\external\Binaries\V3\Microsoft.Data.Services.Client.dll + + + False + ..\..\external\Binaries\V3\Microsoft.Data.Services.Design.dll + + + + + + + False + ..\..\external\Binaries\V3\System.Spatial.dll + + + + + + + + compile; build; native; contentfiles; analyzers; buildtransitive + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + 17.14.0 + + + + + + + + License.txt + Always + true + + + Resources\ExtensionIcon.png + Always + true + + + Resources\Icon.png + + + + + {86faaaec-3bbd-479c-8a44-d2715a6bd4c1} + Microsoft.OData.CodeGen + + + + + + diff --git a/src/ODataConnectedService_VS2026Plus/OcsKey.snk b/src/ODataConnectedService_VS2026Plus/OcsKey.snk new file mode 100644 index 0000000000000000000000000000000000000000..04e7701807ae221c5deef623d90aee700518e848 GIT binary patch literal 596 zcmV-a0;~N80ssI2Bme+XQ$aES1ONa50096!FTBIWSzAI9B6?h)`y55foRlDzgA^cE zJ;7<+gnEi!;Aj^M(Fg+Gr3=K^G%O#AqN|@LwCbES;|ne=wN@N zOSzI%W>Q@5{65;qO2LLY{3!2}T#dJ;LkDsCL8?9IsMDF53j)gMUV~0uDbmaV3AoGe zNWQT#NIuFDqD6$DBw~k$ba?3~dBGvh(9oFu!?L?ka#NuQ;?Qj-tD012)Z)i&K>ALp z-2X3FJ3NPy+GO{Wk|6M##|4Koe=`ta0#CUwV|<8ObU56k`k(@%R@wA5FT3Zl^uH&> zQ(4GJ_*uHQ&GpQ+;JkHX&sj}UNFLpVHspIaN5I-!dhY31;dJU-1l=@fg zQShjhvO#3VDGVWa>5pzp2jCqp$vPTh;|s_{c{Z}i00EF|tdr42_#J^8Ynoj3h6Aa+ z8o}T@i6M~E3eh)sv(drBl3wHZ@0PL-{490=L*O9m3MO>ex{FbJ*cxY|SXBj8oWB zSxVs}W|LNI2*OaBuwJ6FubV*XwO4H!;%c-FGu7JL!e8R5TQ0fRQJFkA0K}956-3JK#oqSM?-0mjC2Gfna@jh*Qza2WyxrUW_;CFqj+!P_e{)>u + + + + ODataConnectedService_VS2026Plus + OData Connected Service for V1-V4 with .NET 10+ support. + https://github.com/odata/ODataConnectedService + License.txt + Resources\ExtensionIcon.png + OData Connected Service + true + + + + amd64 + + + amd64 + + + amd64 + + + arm64 + + + arm64 + + + arm64 + + + + + + + + + + + + + From e04c5e840d296ceb6de32bdfcdd6b56a605ccac5 Mon Sep 17 00:00:00 2001 From: Samuel Wanjohi Date: Fri, 9 Jan 2026 22:11:53 +0300 Subject: [PATCH 07/14] remove VS2026PLUS --- ODataCodeGenTools.sln | 16 -- azure-pipelines.yml | 80 +----- .../Common/ProjectHelper.cs | 39 +++ .../ConnectedServicePackageInstaller.cs | 20 +- .../source.extension.vsixmanifest | 20 +- .../ODataConnectedService_VS2026Plus.csproj | 131 --------- .../OcsKey.snk | Bin 596 -> 0 bytes .../Properties/AssemblyInfo.cs | 34 --- .../source.extension.vsixmanifest | 43 --- .../ODataConnectedService.Tests.csproj | 1 + .../ConnectedServicePackageInstallerTests.cs | 250 ++++++++++++++++++ 11 files changed, 320 insertions(+), 314 deletions(-) delete mode 100644 src/ODataConnectedService_VS2026Plus/ODataConnectedService_VS2026Plus.csproj delete mode 100644 src/ODataConnectedService_VS2026Plus/OcsKey.snk delete mode 100644 src/ODataConnectedService_VS2026Plus/Properties/AssemblyInfo.cs delete mode 100644 src/ODataConnectedService_VS2026Plus/source.extension.vsixmanifest create mode 100644 test/ODataConnectedService.Tests/PackageInstallation/ConnectedServicePackageInstallerTests.cs diff --git a/ODataCodeGenTools.sln b/ODataCodeGenTools.sln index 8e87b14b..dc349366 100644 --- a/ODataCodeGenTools.sln +++ b/ODataCodeGenTools.sln @@ -26,8 +26,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{87FB2A7F-D EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{6500B077-1097-408B-B99A-6105EE8F4400}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ODataConnectedService_VS2026Plus", "src\ODataConnectedService_VS2026Plus\ODataConnectedService_VS2026Plus.csproj", "{A1558D4D-3675-4E76-9659-282E19772DEC}" -EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -110,18 +108,6 @@ Global {FE62623D-B8AF-49E2-AEB2-4EF50419777A}.Release|arm64.Build.0 = Release|Any CPU {FE62623D-B8AF-49E2-AEB2-4EF50419777A}.Release|x86.ActiveCfg = Release|Any CPU {FE62623D-B8AF-49E2-AEB2-4EF50419777A}.Release|x86.Build.0 = Release|Any CPU - {A1558D4D-3675-4E76-9659-282E19772DEC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {A1558D4D-3675-4E76-9659-282E19772DEC}.Debug|Any CPU.Build.0 = Debug|Any CPU - {A1558D4D-3675-4E76-9659-282E19772DEC}.Debug|arm64.ActiveCfg = Debug|arm64 - {A1558D4D-3675-4E76-9659-282E19772DEC}.Debug|arm64.Build.0 = Debug|arm64 - {A1558D4D-3675-4E76-9659-282E19772DEC}.Debug|x86.ActiveCfg = Debug|x86 - {A1558D4D-3675-4E76-9659-282E19772DEC}.Debug|x86.Build.0 = Debug|x86 - {A1558D4D-3675-4E76-9659-282E19772DEC}.Release|Any CPU.ActiveCfg = Release|Any CPU - {A1558D4D-3675-4E76-9659-282E19772DEC}.Release|Any CPU.Build.0 = Release|Any CPU - {A1558D4D-3675-4E76-9659-282E19772DEC}.Release|arm64.ActiveCfg = Release|arm64 - {A1558D4D-3675-4E76-9659-282E19772DEC}.Release|arm64.Build.0 = Release|arm64 - {A1558D4D-3675-4E76-9659-282E19772DEC}.Release|x86.ActiveCfg = Release|x86 - {A1558D4D-3675-4E76-9659-282E19772DEC}.Release|x86.Build.0 = Release|x86 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -134,7 +120,6 @@ Global {61E9E534-C672-4109-A6E1-9AA6F14B6C54} = {6500B077-1097-408B-B99A-6105EE8F4400} {15769DAB-F3B0-4E82-A125-CF55ACB04D07} = {6500B077-1097-408B-B99A-6105EE8F4400} {FE62623D-B8AF-49E2-AEB2-4EF50419777A} = {87FB2A7F-D758-4DBF-8B36-0007A3E156E9} - {A1558D4D-3675-4E76-9659-282E19772DEC} = {6500B077-1097-408B-B99A-6105EE8F4400} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {FD696714-8F52-4A00-93F4-43C843F184DB} @@ -142,7 +127,6 @@ Global GlobalSection(SharedMSBuildProjectFiles) = preSolution src\ODataConnectedService.Shared\ODataConnectedService.Shared.projitems*{15769dab-f3b0-4e82-a125-cf55acb04d07}*SharedItemsImports = 4 src\ODataConnectedService.Shared\ODataConnectedService.Shared.projitems*{61e9e534-c672-4109-a6e1-9aa6f14b6c54}*SharedItemsImports = 13 - src\ODataConnectedService.Shared\ODataConnectedService.Shared.projitems*{a1558d4d-3675-4e76-9659-282e19772dec}*SharedItemsImports = 4 src\ODataConnectedService.Shared\ODataConnectedService.Shared.projitems*{a8bc5b8e-9ab7-4257-b8f1-e7c62169f9b5}*SharedItemsImports = 4 EndGlobalSection EndGlobal diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 8b815426..ad8f7554 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -17,23 +17,18 @@ variables: productFiles: 'Microsoft.OData.ConnectedService.*?(*.dll|*.config|*.pdb)' productBinPathVs2022Plus: '$(connectedServiceDir)\src\ODataConnectedService_VS2022Plus\bin\$(BuildConfiguration)' productFilesVs2022Plus: 'Microsoft.OData.ConnectedService.VS2022Plus.*?(*.dll|*.config|*.pdb)' - productBinPathVs2026Plus: '$(connectedServiceDir)\src\ODataConnectedService_VS2026Plus\bin\$(BuildConfiguration)' - productFilesVs2026Plus: 'Microsoft.OData.ConnectedService.VS2026Plus.*?(*.dll|*.config|*.pdb)' productFilesODataCLI: 'Microsoft.OData.Cli.*?(*.dll|*.config|*.pdb)' testBinPath: '$(connectedServiceDir)\test\ODataConnectedService.Tests\bin\$(BuildConfiguration)' testBinPathODataCLI: '$(connectedServiceDir)\test\Microsoft.OData.Cli.Tests\bin\$(BuildConfiguration)' vsixPath: '$(productBinPath)' vsixFile: 'Microsoft.OData.ConnectedService.vsix' vsixFileVs2022Plus: 'Microsoft.OData.ConnectedService.VS2022Plus.vsix' - vsixFileVs2026Plus: 'Microsoft.OData.ConnectedService.VS2026Plus.vsix' nupkgFile: 'Microsoft.OData.Cli.nupkg' signingFiles: 'Microsoft.OData.ConnectedService.dll' signingFilesVs2022Plus: 'Microsoft.OData.ConnectedService.VS2022Plus.dll' - signingFilesVs2026Plus: 'Microsoft.OData.ConnectedService.VS2026Plus.dll' signingFilesODataCLI: 'Microsoft.OData.Cli.dll' mainDll: 'Microsoft.OData.ConnectedService.dll' mainDllVs2022Plus: 'Microsoft.OData.ConnectedService.VS2022Plus.dll' - mainDllVs2026Plus: 'Microsoft.OData.ConnectedService.VS2026Plus.dll' mainDllODataCLI: 'Microsoft.OData.Cli.dll' testDll: 'ODataConnectedService.Tests.dll' testDllODataCLI: 'Microsoft.OData.Cli.Tests.dll' @@ -132,14 +127,6 @@ stages: TargetFolder: '$(Build.ArtifactStagingDirectory)\VSIX2022plus' OverWrite: true - - task: CopyFiles@2 - displayName: 'Copy Files - 2026plus VSIX to Artifacts Staging' - inputs: - SourceFolder: '$(productBinPathVs2026Plus)' - Contents: 'Microsoft.OData.ConnectedService.VS2026Plus.vsix' - TargetFolder: '$(Build.ArtifactStagingDirectory)\VSIX2026plus' - OverWrite: true - - task: CopyFiles@2 displayName: 'Copy Files - Stage Product' inputs: @@ -156,14 +143,6 @@ stages: TargetFolder: '$(Build.ArtifactStagingDirectory)\Product2022plus' OverWrite: true - - task: CopyFiles@2 - displayName: 'Copy Files - Stage Product 2026plus' - inputs: - SourceFolder: '$(productBinPathVs2026Plus)' - Contents: '$(productFilesVs2026Plus)' - TargetFolder: '$(Build.ArtifactStagingDirectory)\Product2026plus' - OverWrite: true - - task: PublishBuildArtifacts@1 displayName: 'Publish Artifact - VSIX' inputs: @@ -177,14 +156,7 @@ stages: PathtoPublish: '$(Build.ArtifactStagingDirectory)\VSIX2022Plus' ArtifactName: 'VSIX2022Plus' publishLocation: 'Container' - - - task: PublishBuildArtifacts@1 - displayName: 'Publish Artifact - VSIX2026Plus' - inputs: - PathtoPublish: '$(Build.ArtifactStagingDirectory)\VSIX2026plus' - ArtifactName: 'VSIX2026Plus' - publishLocation: 'Container' - + - task: PublishBuildArtifacts@1 displayName: 'Publish Artifact - ODataCLINupkg' inputs: @@ -296,55 +268,6 @@ stages: ArtifactName: 'SignedVSIX2022Plus' publishLocation: 'Container' -- stage: CodeSign3 - condition: and(succeeded('Build'), not(eq(variables['build.reason'], 'PullRequest'))) - jobs: - - deployment: CodeSign - displayName: Code Signing - pool: - vmImage: windows-latest - environment: Code Sign - Approvals - variables: - - group: Code Signing - strategy: - runOnce: - deploy: - steps: - - task: DotNetCoreCLI@2 - inputs: - command: custom - custom: tool - arguments: install --tool-path . --prerelease sign - displayName: Install SignTool tool - - # Sign the VSIX archive as well as files contained in the VSIX that are listed in config/filelist.txt - # For more info about the Sign CLI tool, visit: https://github.com/dotnet/sign - - task: AzureCLI@2 - inputs: - azureSubscription: 'Code Signing' - scriptType: pscore - scriptLocation: inlineScript - inlineScript: | - .\sign code azure-key-vault ` - "**/*.{dll,vsix,zip,nupkg}" ` - --base-directory "$(Pipeline.Workspace)/VSIX2026Plus" ` - --output "$(Build.ArtifactStagingDirectory)/SignedVSIX2026Plus" ` - --file-list "$(Pipeline.Workspace)/configs/filelist.txt" ` - --publisher-name "OData Labs" ` - --description "OData Connected Service 2026+" ` - --description-url "https://github.com/OData/ODataConnectedService" ` - --azure-key-vault-managed-identity ` - --azure-key-vault-certificate "$(SignKeyVaultCertificate)" ` - --azure-key-vault-url "$(SignKeyVaultUrl)" - displayName: Code signing - VSIX2026Plus - - # Publish the signed VSIX as an artifact so we can download it after the build - - task: PublishBuildArtifacts@1 - displayName: 'Publish Artifact: SignedVSIX2026Plus' - inputs: - PathtoPublish: '$(Build.ArtifactStagingDirectory)/SignedVSIX2026Plus' - ArtifactName: 'SignedVSIX2026Plus' - - stage: CodeSignODataCLI condition: and(succeeded('Build'), not(eq(variables['build.reason'], 'PullRequest'))) jobs: @@ -407,4 +330,3 @@ stages: PathtoPublish: '$(Build.ArtifactStagingDirectory)\SignedODataCLINupkg' ArtifactName: SignedODataCLINupkg publishLocation: 'Container' - \ No newline at end of file diff --git a/src/ODataConnectedService.Shared/Common/ProjectHelper.cs b/src/ODataConnectedService.Shared/Common/ProjectHelper.cs index addf0d8c..f708435a 100644 --- a/src/ODataConnectedService.Shared/Common/ProjectHelper.cs +++ b/src/ODataConnectedService.Shared/Common/ProjectHelper.cs @@ -53,5 +53,44 @@ public static LanguageOption GetLanguageOption(this Project project) return LanguageOption.GenerateCSharpCode; } } + + + ///// + ///// Gets the target frameworks for the current project. + ///// + ///// A string of the target frameworks for the provided project + //public static string GetProjectTargetFrameworks(this Project project) + //{ + // return threadHelper.RunInUiThreadAsync(() => + // { + // if (project == null) + // { + // return string.Empty; + // } + + // // Try to get TargetFrameworks (multi-targeting) + // var targetFrameworks = project?.Properties.Item("TargetFrameworks")?.Value as string; + // if (!string.IsNullOrEmpty(targetFrameworks)) + // { + // return targetFrameworks; + // } + + // // Fallback to TargetFramework (single target) + // var targetFramework = project?.Properties.Item("TargetFramework")?.Value as string; + // if (!string.IsNullOrEmpty(targetFramework)) + // { + // return targetFramework; + // } + + // // Legacy approach for .NET Framework projects + // var targetFrameworkMoniker = project?.Properties?.Item("TargetFrameworkMoniker")?.Value as string; + // if (!string.IsNullOrEmpty(targetFrameworkMoniker)) + // { + // return targetFrameworkMoniker; + // } + + // return string.Empty; + // }); + //} } } diff --git a/src/ODataConnectedService.Shared/ConnectedServicePackageInstaller.cs b/src/ODataConnectedService.Shared/ConnectedServicePackageInstaller.cs index 3b39f0c9..8476fdfc 100644 --- a/src/ODataConnectedService.Shared/ConnectedServicePackageInstaller.cs +++ b/src/ODataConnectedService.Shared/ConnectedServicePackageInstaller.cs @@ -30,6 +30,8 @@ public class ConnectedServicePackageInstaller : IPackageInstaller public IVsPackageInstallerServices PackageInstallerServices { get; protected set; } + private readonly string TargetFrameworkMoniker; + /// /// Creates an instance of /// @@ -42,6 +44,10 @@ public ConnectedServicePackageInstaller(ConnectedServiceHandlerContext context, this.Context = context; this.Project = project; this.MessageLogger = messageLogger; + + Shell.ThreadHelper.ThrowIfNotOnUIThread(); + + TargetFrameworkMoniker = (string)this.Project.Properties?.Item("TargetFrameworkMoniker")?.Value; } /// @@ -70,7 +76,19 @@ public async Task CheckAndInstallNuGetPackageAsync(string packageSource, string { if (!PackageInstallerServices.IsPackageInstalled(this.Project, packageName)) { - PackageInstaller.InstallLatestPackage(packageSource, this.Project, packageName, true, false); + // For .NET Core and later, always install the latest version of the package + if (!string.IsNullOrEmpty(TargetFrameworkMoniker) && int.TryParse(TargetFrameworkMoniker.Substring(3), out var dotnetVersion) && dotnetVersion >= 0) + { + // No need to install System.Text.Json as it is part of the shared framework + if (!packageName.Equals("System.Text.Json", StringComparison.Ordinal)) + { + PackageInstaller.InstallLatestPackage(packageSource, this.Project, packageName, true, false); + } + } + else + { + PackageInstaller.InstallPackage(packageSource, this.Project, packageName, (string)null, false); + } await (this.MessageLogger?.WriteMessageAsync(LogMessageCategory.Information, $"Nuget Package \"{packageName}\" for OData client was added.")).ConfigureAwait(false); } diff --git a/src/ODataConnectedService_VS2022Plus/source.extension.vsixmanifest b/src/ODataConnectedService_VS2022Plus/source.extension.vsixmanifest index 4d5e0bf5..c17762a6 100644 --- a/src/ODataConnectedService_VS2022Plus/source.extension.vsixmanifest +++ b/src/ODataConnectedService_VS2022Plus/source.extension.vsixmanifest @@ -1,7 +1,7 @@ - + OData Connected Service 2022+ OData Connected Service for V1-V4 https://github.com/odata/ODataConnectedService @@ -10,33 +10,33 @@ OData Connected Service - + amd64 - + amd64 - + amd64 - + arm64 - + arm64 - + arm64 - + - + - + \ No newline at end of file diff --git a/src/ODataConnectedService_VS2026Plus/ODataConnectedService_VS2026Plus.csproj b/src/ODataConnectedService_VS2026Plus/ODataConnectedService_VS2026Plus.csproj deleted file mode 100644 index ee60e910..00000000 --- a/src/ODataConnectedService_VS2026Plus/ODataConnectedService_VS2026Plus.csproj +++ /dev/null @@ -1,131 +0,0 @@ - - - - 18.0 - $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) - - - - Debug - AnyCPU - 2.0 - {82b43b9b-a64c-4715-b499-d71e9ca2bd60};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} - {A1558D4D-3675-4E76-9659-282E19772DEC} - Library - Properties - Microsoft.OData.ConnectedService.VS2026Plus - Microsoft.OData.ConnectedService.VS2026Plus - v4.7.2 - false - true - true - false - false - true - true - Program - $(DevEnvDir)devenv.exe - /rootsuffix Exp - - - true - full - false - bin\Debug\ - DEBUG;TRACE;VS2026PLUS - prompt - 4 - - - pdbonly - true - bin\Release\ - TRACE;VS2026PLUS - prompt - 4 - - - true - - - OcsKey.snk - - - - - - - - Designer - - - - - False - ..\..\external\Binaries\V3\Microsoft.Data.Edm.dll - - - False - ..\..\external\Binaries\V3\Microsoft.Data.OData.dll - - - False - ..\..\external\Binaries\V3\Microsoft.Data.Services.Client.dll - - - False - ..\..\external\Binaries\V3\Microsoft.Data.Services.Design.dll - - - - - - - False - ..\..\external\Binaries\V3\System.Spatial.dll - - - - - - - - compile; build; native; contentfiles; analyzers; buildtransitive - - - - runtime; build; native; contentfiles; analyzers; buildtransitive - all - - - 17.14.0 - - - - - - - - License.txt - Always - true - - - Resources\ExtensionIcon.png - Always - true - - - Resources\Icon.png - - - - - {86faaaec-3bbd-479c-8a44-d2715a6bd4c1} - Microsoft.OData.CodeGen - - - - - - diff --git a/src/ODataConnectedService_VS2026Plus/OcsKey.snk b/src/ODataConnectedService_VS2026Plus/OcsKey.snk deleted file mode 100644 index 04e7701807ae221c5deef623d90aee700518e848..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 596 zcmV-a0;~N80ssI2Bme+XQ$aES1ONa50096!FTBIWSzAI9B6?h)`y55foRlDzgA^cE zJ;7<+gnEi!;Aj^M(Fg+Gr3=K^G%O#AqN|@LwCbES;|ne=wN@N zOSzI%W>Q@5{65;qO2LLY{3!2}T#dJ;LkDsCL8?9IsMDF53j)gMUV~0uDbmaV3AoGe zNWQT#NIuFDqD6$DBw~k$ba?3~dBGvh(9oFu!?L?ka#NuQ;?Qj-tD012)Z)i&K>ALp z-2X3FJ3NPy+GO{Wk|6M##|4Koe=`ta0#CUwV|<8ObU56k`k(@%R@wA5FT3Zl^uH&> zQ(4GJ_*uHQ&GpQ+;JkHX&sj}UNFLpVHspIaN5I-!dhY31;dJU-1l=@fg zQShjhvO#3VDGVWa>5pzp2jCqp$vPTh;|s_{c{Z}i00EF|tdr42_#J^8Ynoj3h6Aa+ z8o}T@i6M~E3eh)sv(drBl3wHZ@0PL-{490=L*O9m3MO>ex{FbJ*cxY|SXBj8oWB zSxVs}W|LNI2*OaBuwJ6FubV*XwO4H!;%c-FGu7JL!e8R5TQ0fRQJFkA0K}956-3JK#oqSM?-0mjC2Gfna@jh*Qza2WyxrUW_;CFqj+!P_e{)>u - - - - ODataConnectedService_VS2026Plus - OData Connected Service for V1-V4 with .NET 10+ support. - https://github.com/odata/ODataConnectedService - License.txt - Resources\ExtensionIcon.png - OData Connected Service - true - - - - amd64 - - - amd64 - - - amd64 - - - arm64 - - - arm64 - - - arm64 - - - - - - - - - - - - - diff --git a/test/ODataConnectedService.Tests/ODataConnectedService.Tests.csproj b/test/ODataConnectedService.Tests/ODataConnectedService.Tests.csproj index 1fd79c09..cd8d1f82 100644 --- a/test/ODataConnectedService.Tests/ODataConnectedService.Tests.csproj +++ b/test/ODataConnectedService.Tests/ODataConnectedService.Tests.csproj @@ -73,6 +73,7 @@ + diff --git a/test/ODataConnectedService.Tests/PackageInstallation/ConnectedServicePackageInstallerTests.cs b/test/ODataConnectedService.Tests/PackageInstallation/ConnectedServicePackageInstallerTests.cs new file mode 100644 index 00000000..c37f70c7 --- /dev/null +++ b/test/ODataConnectedService.Tests/PackageInstallation/ConnectedServicePackageInstallerTests.cs @@ -0,0 +1,250 @@ +//----------------------------------------------------------------------------- +// +// Copyright (c) .NET Foundation and Contributors. All rights reserved. +// See License.txt in the project root for license information. +// +//---------------------------------------------------------------------------- + +using System; +using System.Threading.Tasks; +using EnvDTE; +using Microsoft.OData.CodeGen.Logging; +using Microsoft.OData.CodeGen.Models; +using Microsoft.OData.ConnectedService; +using Microsoft.OData.ConnectedService.Tests.TestHelpers; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Moq; +using NuGet.VisualStudio; + +namespace ODataConnectedService.Tests.PackageInstallation +{ + [TestClass] + public class ConnectedServicePackageInstallerTests + { + private Mock packageInstallerMock; + private Mock packageInstallerServicesMock; + private Mock messageLoggerMock; + private Mock projectMock; + private Mock propertiesMock; + private TestConnectedServiceHandlerContext context; + + [TestInitialize] + public void Initialize() + { + this.packageInstallerMock = new Mock(); + this.packageInstallerServicesMock = new Mock(); + this.messageLoggerMock = new Mock(); + this.projectMock = new Mock(); + this.propertiesMock = new Mock(); + + var serviceConfig = new ServiceConfiguration { ServiceName = "TestService" }; + var serviceInstance = new ODataConnectedServiceInstance + { + ServiceConfig = serviceConfig, + Name = "TestService" + }; + + var handlerHelper = new TestConnectedServiceHandlerHelper(); + this.context = new TestConnectedServiceHandlerContext(serviceInstance, handlerHelper); + } + + [TestMethod] + public async Task CheckAndInstallNuGetPackageAsync_ShouldInstallPackage_WhenPackageNotInstalledAndFrameworkIsDotNetAsync() + { + // Arrange + const string packageSource = "https://api.nuget.org/v3/index.json"; + const string packageName = "Microsoft.OData.Client"; + + this.SetupProject(".NETFramework,Version=v4.7.2"); + this.packageInstallerServicesMock + .Setup(s => s.IsPackageInstalled(It.IsAny(), packageName)) + .Returns(false); + + var installer = this.CreatePackageInstaller(); + + // Act + await installer.CheckAndInstallNuGetPackageAsync(packageSource, packageName).ConfigureAwait(false); + + // Assert + this.packageInstallerMock.Verify( + p => p.InstallLatestPackage(packageSource, this.projectMock.Object, packageName, true, false), + Times.Once); + this.messageLoggerMock.Verify( + m => m.WriteMessageAsync(LogMessageCategory.Information, It.Is(s => s.Contains("added")), It.IsAny()), + Times.Once); + } + + [TestMethod] + public async Task CheckAndInstallNuGetPackageAsync_ShouldSkipInstallation_WhenPackageAlreadyInstalledAsync() + { + // Arrange + const string packageSource = "https://api.nuget.org/v3/index.json"; + const string packageName = "Microsoft.OData.Client"; + + this.SetupProject(".NETFramework,Version=v4.7.2"); + this.packageInstallerServicesMock + .Setup(s => s.IsPackageInstalled(It.IsAny(), packageName)) + .Returns(true); + + var installer = this.CreatePackageInstaller(); + + // Act + await installer.CheckAndInstallNuGetPackageAsync(packageSource, packageName).ConfigureAwait(false); + + // Assert + this.packageInstallerMock.Verify( + p => p.InstallLatestPackage(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny()), + Times.Never); + this.packageInstallerMock.Verify( + p => p.InstallPackage(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny()), + Times.Never); + this.messageLoggerMock.Verify( + m => m.WriteMessageAsync(LogMessageCategory.Information, It.Is(s => s.Contains("already installed")), It.IsAny()), + Times.Once); + } + + [TestMethod] + public async Task CheckAndInstallNuGetPackageAsync_ShouldSkipSystemTextJson_WhenFrameworkIsDotNetAsync() + { + // Arrange + const string packageSource = "https://api.nuget.org/v3/index.json"; + const string packageName = "System.Text.Json"; + + this.SetupProject(".NETFramework,Version=v4.7.2"); + this.packageInstallerServicesMock + .Setup(s => s.IsPackageInstalled(It.IsAny(), packageName)) + .Returns(false); + + var installer = this.CreatePackageInstaller(); + + // Act + await installer.CheckAndInstallNuGetPackageAsync(packageSource, packageName).ConfigureAwait(false); + + // Assert + this.packageInstallerMock.Verify( + p => p.InstallLatestPackage(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny()), + Times.Never); + this.packageInstallerMock.Verify( + p => p.InstallPackage(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny()), + Times.Never); + } + + [TestMethod] + public async Task CheckAndInstallNuGetPackageAsync_ShouldUseInstallPackage_WhenFrameworkVersionCannotBeParsedAsync() + { + // Arrange + const string packageSource = "https://api.nuget.org/v3/index.json"; + const string packageName = "Microsoft.OData.Client"; + + this.SetupProject("InvalidFrameworkVersion"); + this.packageInstallerServicesMock + .Setup(s => s.IsPackageInstalled(It.IsAny(), packageName)) + .Returns(false); + + var installer = this.CreatePackageInstaller(); + + // Act + await installer.CheckAndInstallNuGetPackageAsync(packageSource, packageName).ConfigureAwait(false); + + // Assert + this.packageInstallerMock.Verify( + p => p.InstallPackage(packageSource, this.projectMock.Object, packageName, (string)null, false), + Times.Once); + this.messageLoggerMock.Verify( + m => m.WriteMessageAsync(LogMessageCategory.Information, It.Is(s => s.Contains("added")), It.IsAny()), + Times.Once); + } + + [TestMethod] + public async Task CheckAndInstallNuGetPackageAsync_ShouldLogError_WhenInstallationFailsAsync() + { + // Arrange + const string packageSource = "https://api.nuget.org/v3/index.json"; + const string packageName = "Microsoft.OData.Client"; + const string errorMessage = "Installation failed"; + + this.SetupProject(".NETFramework,Version=v4.7.2"); + this.packageInstallerServicesMock + .Setup(s => s.IsPackageInstalled(It.IsAny(), packageName)) + .Returns(false); + this.packageInstallerMock + .Setup(p => p.InstallLatestPackage(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) + .Throws(new Exception(errorMessage)); + + var installer = this.CreatePackageInstaller(); + + // Act + await installer.CheckAndInstallNuGetPackageAsync(packageSource, packageName).ConfigureAwait(false); + + // Assert + this.messageLoggerMock.Verify( + m => m.WriteMessageAsync(LogMessageCategory.Error, It.Is(s => s.Contains("not installed") && s.Contains(errorMessage)), It.IsAny()), + Times.Once); + } + + [TestMethod] + public async Task CheckAndInstallNuGetPackageAsync_ShouldInstallLatestPackage_WhenFrameworkVersionIsNet5OrHigherAsync() + { + // Arrange + const string packageSource = "https://api.nuget.org/v3/index.json"; + const string packageName = "Microsoft.OData.Client"; + + this.SetupProject(".NETCoreApp,Version=v5.0"); + this.packageInstallerServicesMock + .Setup(s => s.IsPackageInstalled(It.IsAny(), packageName)) + .Returns(false); + + var installer = this.CreatePackageInstaller(); + + // Act + await installer.CheckAndInstallNuGetPackageAsync(packageSource, packageName).ConfigureAwait(false); + + // Assert + this.packageInstallerMock.Verify( + p => p.InstallLatestPackage(packageSource, this.projectMock.Object, packageName, true, false), + Times.Once); + } + + [TestMethod] + public async Task CheckAndInstallNuGetPackageAsync_ShouldHandleNullTargetFrameworkMonikerAsync() + { + // Arrange + const string packageSource = "https://api.nuget.org/v3/index.json"; + const string packageName = "Microsoft.OData.Client"; + + this.SetupProject(null); + this.packageInstallerServicesMock + .Setup(s => s.IsPackageInstalled(It.IsAny(), packageName)) + .Returns(false); + + var installer = this.CreatePackageInstaller(); + + // Act + await installer.CheckAndInstallNuGetPackageAsync(packageSource, packageName).ConfigureAwait(false); + + // Assert + this.packageInstallerMock.Verify( + p => p.InstallPackage(packageSource, this.projectMock.Object, packageName, (string)null, false), + Times.Once); + } + + private ConnectedServicePackageInstaller CreatePackageInstaller() + { + var installer = new ConnectedServicePackageInstaller(this.context, this.projectMock.Object, this.messageLoggerMock.Object); + + return installer; + } + + private void SetupProject(string targetFrameworkMoniker) + { + var propertyMock = new Mock(); + propertyMock.SetupGet(p => p.Value).Returns(targetFrameworkMoniker); + + this.propertiesMock + .Setup(p => p.Item("TargetFrameworkMoniker")) + .Returns(propertyMock.Object); + + this.projectMock.SetupGet(p => p.Properties).Returns(this.propertiesMock.Object); + } + } +} From 02b3240f9b1790760e588978b45f1ff0d32cca6e Mon Sep 17 00:00:00 2001 From: Samuel Wanjohi Date: Sun, 11 Jan 2026 20:05:07 +0300 Subject: [PATCH 08/14] no need to find use the targetFramework --- .../Common/ProjectHelper.cs | 39 --- .../ConnectedServicePackageInstaller.cs | 21 +- .../ODataConnectedService.Tests.csproj | 1 - .../ConnectedServicePackageInstallerTests.cs | 250 ------------------ 4 files changed, 2 insertions(+), 309 deletions(-) delete mode 100644 test/ODataConnectedService.Tests/PackageInstallation/ConnectedServicePackageInstallerTests.cs diff --git a/src/ODataConnectedService.Shared/Common/ProjectHelper.cs b/src/ODataConnectedService.Shared/Common/ProjectHelper.cs index f708435a..addf0d8c 100644 --- a/src/ODataConnectedService.Shared/Common/ProjectHelper.cs +++ b/src/ODataConnectedService.Shared/Common/ProjectHelper.cs @@ -53,44 +53,5 @@ public static LanguageOption GetLanguageOption(this Project project) return LanguageOption.GenerateCSharpCode; } } - - - ///// - ///// Gets the target frameworks for the current project. - ///// - ///// A string of the target frameworks for the provided project - //public static string GetProjectTargetFrameworks(this Project project) - //{ - // return threadHelper.RunInUiThreadAsync(() => - // { - // if (project == null) - // { - // return string.Empty; - // } - - // // Try to get TargetFrameworks (multi-targeting) - // var targetFrameworks = project?.Properties.Item("TargetFrameworks")?.Value as string; - // if (!string.IsNullOrEmpty(targetFrameworks)) - // { - // return targetFrameworks; - // } - - // // Fallback to TargetFramework (single target) - // var targetFramework = project?.Properties.Item("TargetFramework")?.Value as string; - // if (!string.IsNullOrEmpty(targetFramework)) - // { - // return targetFramework; - // } - - // // Legacy approach for .NET Framework projects - // var targetFrameworkMoniker = project?.Properties?.Item("TargetFrameworkMoniker")?.Value as string; - // if (!string.IsNullOrEmpty(targetFrameworkMoniker)) - // { - // return targetFrameworkMoniker; - // } - - // return string.Empty; - // }); - //} } } diff --git a/src/ODataConnectedService.Shared/ConnectedServicePackageInstaller.cs b/src/ODataConnectedService.Shared/ConnectedServicePackageInstaller.cs index 8476fdfc..50b2a7fe 100644 --- a/src/ODataConnectedService.Shared/ConnectedServicePackageInstaller.cs +++ b/src/ODataConnectedService.Shared/ConnectedServicePackageInstaller.cs @@ -30,8 +30,6 @@ public class ConnectedServicePackageInstaller : IPackageInstaller public IVsPackageInstallerServices PackageInstallerServices { get; protected set; } - private readonly string TargetFrameworkMoniker; - /// /// Creates an instance of /// @@ -44,10 +42,6 @@ public ConnectedServicePackageInstaller(ConnectedServiceHandlerContext context, this.Context = context; this.Project = project; this.MessageLogger = messageLogger; - - Shell.ThreadHelper.ThrowIfNotOnUIThread(); - - TargetFrameworkMoniker = (string)this.Project.Properties?.Item("TargetFrameworkMoniker")?.Value; } /// @@ -76,19 +70,8 @@ public async Task CheckAndInstallNuGetPackageAsync(string packageSource, string { if (!PackageInstallerServices.IsPackageInstalled(this.Project, packageName)) { - // For .NET Core and later, always install the latest version of the package - if (!string.IsNullOrEmpty(TargetFrameworkMoniker) && int.TryParse(TargetFrameworkMoniker.Substring(3), out var dotnetVersion) && dotnetVersion >= 0) - { - // No need to install System.Text.Json as it is part of the shared framework - if (!packageName.Equals("System.Text.Json", StringComparison.Ordinal)) - { - PackageInstaller.InstallLatestPackage(packageSource, this.Project, packageName, true, false); - } - } - else - { - PackageInstaller.InstallPackage(packageSource, this.Project, packageName, (string)null, false); - } + //PackageInstaller.InstallPackage(packageSource, this.Project, packageName, (string)null, false); + PackageInstaller.InstallLatestPackage(packageSource, this.Project, packageName, true, false); await (this.MessageLogger?.WriteMessageAsync(LogMessageCategory.Information, $"Nuget Package \"{packageName}\" for OData client was added.")).ConfigureAwait(false); } diff --git a/test/ODataConnectedService.Tests/ODataConnectedService.Tests.csproj b/test/ODataConnectedService.Tests/ODataConnectedService.Tests.csproj index cd8d1f82..1fd79c09 100644 --- a/test/ODataConnectedService.Tests/ODataConnectedService.Tests.csproj +++ b/test/ODataConnectedService.Tests/ODataConnectedService.Tests.csproj @@ -73,7 +73,6 @@ - diff --git a/test/ODataConnectedService.Tests/PackageInstallation/ConnectedServicePackageInstallerTests.cs b/test/ODataConnectedService.Tests/PackageInstallation/ConnectedServicePackageInstallerTests.cs deleted file mode 100644 index c37f70c7..00000000 --- a/test/ODataConnectedService.Tests/PackageInstallation/ConnectedServicePackageInstallerTests.cs +++ /dev/null @@ -1,250 +0,0 @@ -//----------------------------------------------------------------------------- -// -// Copyright (c) .NET Foundation and Contributors. All rights reserved. -// See License.txt in the project root for license information. -// -//---------------------------------------------------------------------------- - -using System; -using System.Threading.Tasks; -using EnvDTE; -using Microsoft.OData.CodeGen.Logging; -using Microsoft.OData.CodeGen.Models; -using Microsoft.OData.ConnectedService; -using Microsoft.OData.ConnectedService.Tests.TestHelpers; -using Microsoft.VisualStudio.TestTools.UnitTesting; -using Moq; -using NuGet.VisualStudio; - -namespace ODataConnectedService.Tests.PackageInstallation -{ - [TestClass] - public class ConnectedServicePackageInstallerTests - { - private Mock packageInstallerMock; - private Mock packageInstallerServicesMock; - private Mock messageLoggerMock; - private Mock projectMock; - private Mock propertiesMock; - private TestConnectedServiceHandlerContext context; - - [TestInitialize] - public void Initialize() - { - this.packageInstallerMock = new Mock(); - this.packageInstallerServicesMock = new Mock(); - this.messageLoggerMock = new Mock(); - this.projectMock = new Mock(); - this.propertiesMock = new Mock(); - - var serviceConfig = new ServiceConfiguration { ServiceName = "TestService" }; - var serviceInstance = new ODataConnectedServiceInstance - { - ServiceConfig = serviceConfig, - Name = "TestService" - }; - - var handlerHelper = new TestConnectedServiceHandlerHelper(); - this.context = new TestConnectedServiceHandlerContext(serviceInstance, handlerHelper); - } - - [TestMethod] - public async Task CheckAndInstallNuGetPackageAsync_ShouldInstallPackage_WhenPackageNotInstalledAndFrameworkIsDotNetAsync() - { - // Arrange - const string packageSource = "https://api.nuget.org/v3/index.json"; - const string packageName = "Microsoft.OData.Client"; - - this.SetupProject(".NETFramework,Version=v4.7.2"); - this.packageInstallerServicesMock - .Setup(s => s.IsPackageInstalled(It.IsAny(), packageName)) - .Returns(false); - - var installer = this.CreatePackageInstaller(); - - // Act - await installer.CheckAndInstallNuGetPackageAsync(packageSource, packageName).ConfigureAwait(false); - - // Assert - this.packageInstallerMock.Verify( - p => p.InstallLatestPackage(packageSource, this.projectMock.Object, packageName, true, false), - Times.Once); - this.messageLoggerMock.Verify( - m => m.WriteMessageAsync(LogMessageCategory.Information, It.Is(s => s.Contains("added")), It.IsAny()), - Times.Once); - } - - [TestMethod] - public async Task CheckAndInstallNuGetPackageAsync_ShouldSkipInstallation_WhenPackageAlreadyInstalledAsync() - { - // Arrange - const string packageSource = "https://api.nuget.org/v3/index.json"; - const string packageName = "Microsoft.OData.Client"; - - this.SetupProject(".NETFramework,Version=v4.7.2"); - this.packageInstallerServicesMock - .Setup(s => s.IsPackageInstalled(It.IsAny(), packageName)) - .Returns(true); - - var installer = this.CreatePackageInstaller(); - - // Act - await installer.CheckAndInstallNuGetPackageAsync(packageSource, packageName).ConfigureAwait(false); - - // Assert - this.packageInstallerMock.Verify( - p => p.InstallLatestPackage(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny()), - Times.Never); - this.packageInstallerMock.Verify( - p => p.InstallPackage(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny()), - Times.Never); - this.messageLoggerMock.Verify( - m => m.WriteMessageAsync(LogMessageCategory.Information, It.Is(s => s.Contains("already installed")), It.IsAny()), - Times.Once); - } - - [TestMethod] - public async Task CheckAndInstallNuGetPackageAsync_ShouldSkipSystemTextJson_WhenFrameworkIsDotNetAsync() - { - // Arrange - const string packageSource = "https://api.nuget.org/v3/index.json"; - const string packageName = "System.Text.Json"; - - this.SetupProject(".NETFramework,Version=v4.7.2"); - this.packageInstallerServicesMock - .Setup(s => s.IsPackageInstalled(It.IsAny(), packageName)) - .Returns(false); - - var installer = this.CreatePackageInstaller(); - - // Act - await installer.CheckAndInstallNuGetPackageAsync(packageSource, packageName).ConfigureAwait(false); - - // Assert - this.packageInstallerMock.Verify( - p => p.InstallLatestPackage(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny()), - Times.Never); - this.packageInstallerMock.Verify( - p => p.InstallPackage(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny()), - Times.Never); - } - - [TestMethod] - public async Task CheckAndInstallNuGetPackageAsync_ShouldUseInstallPackage_WhenFrameworkVersionCannotBeParsedAsync() - { - // Arrange - const string packageSource = "https://api.nuget.org/v3/index.json"; - const string packageName = "Microsoft.OData.Client"; - - this.SetupProject("InvalidFrameworkVersion"); - this.packageInstallerServicesMock - .Setup(s => s.IsPackageInstalled(It.IsAny(), packageName)) - .Returns(false); - - var installer = this.CreatePackageInstaller(); - - // Act - await installer.CheckAndInstallNuGetPackageAsync(packageSource, packageName).ConfigureAwait(false); - - // Assert - this.packageInstallerMock.Verify( - p => p.InstallPackage(packageSource, this.projectMock.Object, packageName, (string)null, false), - Times.Once); - this.messageLoggerMock.Verify( - m => m.WriteMessageAsync(LogMessageCategory.Information, It.Is(s => s.Contains("added")), It.IsAny()), - Times.Once); - } - - [TestMethod] - public async Task CheckAndInstallNuGetPackageAsync_ShouldLogError_WhenInstallationFailsAsync() - { - // Arrange - const string packageSource = "https://api.nuget.org/v3/index.json"; - const string packageName = "Microsoft.OData.Client"; - const string errorMessage = "Installation failed"; - - this.SetupProject(".NETFramework,Version=v4.7.2"); - this.packageInstallerServicesMock - .Setup(s => s.IsPackageInstalled(It.IsAny(), packageName)) - .Returns(false); - this.packageInstallerMock - .Setup(p => p.InstallLatestPackage(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) - .Throws(new Exception(errorMessage)); - - var installer = this.CreatePackageInstaller(); - - // Act - await installer.CheckAndInstallNuGetPackageAsync(packageSource, packageName).ConfigureAwait(false); - - // Assert - this.messageLoggerMock.Verify( - m => m.WriteMessageAsync(LogMessageCategory.Error, It.Is(s => s.Contains("not installed") && s.Contains(errorMessage)), It.IsAny()), - Times.Once); - } - - [TestMethod] - public async Task CheckAndInstallNuGetPackageAsync_ShouldInstallLatestPackage_WhenFrameworkVersionIsNet5OrHigherAsync() - { - // Arrange - const string packageSource = "https://api.nuget.org/v3/index.json"; - const string packageName = "Microsoft.OData.Client"; - - this.SetupProject(".NETCoreApp,Version=v5.0"); - this.packageInstallerServicesMock - .Setup(s => s.IsPackageInstalled(It.IsAny(), packageName)) - .Returns(false); - - var installer = this.CreatePackageInstaller(); - - // Act - await installer.CheckAndInstallNuGetPackageAsync(packageSource, packageName).ConfigureAwait(false); - - // Assert - this.packageInstallerMock.Verify( - p => p.InstallLatestPackage(packageSource, this.projectMock.Object, packageName, true, false), - Times.Once); - } - - [TestMethod] - public async Task CheckAndInstallNuGetPackageAsync_ShouldHandleNullTargetFrameworkMonikerAsync() - { - // Arrange - const string packageSource = "https://api.nuget.org/v3/index.json"; - const string packageName = "Microsoft.OData.Client"; - - this.SetupProject(null); - this.packageInstallerServicesMock - .Setup(s => s.IsPackageInstalled(It.IsAny(), packageName)) - .Returns(false); - - var installer = this.CreatePackageInstaller(); - - // Act - await installer.CheckAndInstallNuGetPackageAsync(packageSource, packageName).ConfigureAwait(false); - - // Assert - this.packageInstallerMock.Verify( - p => p.InstallPackage(packageSource, this.projectMock.Object, packageName, (string)null, false), - Times.Once); - } - - private ConnectedServicePackageInstaller CreatePackageInstaller() - { - var installer = new ConnectedServicePackageInstaller(this.context, this.projectMock.Object, this.messageLoggerMock.Object); - - return installer; - } - - private void SetupProject(string targetFrameworkMoniker) - { - var propertyMock = new Mock(); - propertyMock.SetupGet(p => p.Value).Returns(targetFrameworkMoniker); - - this.propertiesMock - .Setup(p => p.Item("TargetFrameworkMoniker")) - .Returns(propertyMock.Object); - - this.projectMock.SetupGet(p => p.Properties).Returns(this.propertiesMock.Object); - } - } -} From 8a6f672e259f1cc4831ed09427217fccc261b8d9 Mon Sep 17 00:00:00 2001 From: Samuel Wanjohi Date: Mon, 12 Jan 2026 13:03:17 +0300 Subject: [PATCH 09/14] add more tests for CLI --- .../ODataCliFileHandler.cs | 2 +- .../PackageInstallerHelpers.cs | 2 +- src/Microsoft.OData.Cli/ProjectHelper.cs | 24 +++ .../FileHandling/ODataCliFileHandlerTests.cs | 183 ++++++++++++++++++ .../Microsoft.OData.Cli.Tests.csproj | 1 + 5 files changed, 210 insertions(+), 2 deletions(-) create mode 100644 test/Microsoft.OData.Cli.Tests/FileHandling/ODataCliFileHandlerTests.cs diff --git a/src/Microsoft.OData.Cli/ODataCliFileHandler.cs b/src/Microsoft.OData.Cli/ODataCliFileHandler.cs index a98fa9ff..0ae360b2 100644 --- a/src/Microsoft.OData.Cli/ODataCliFileHandler.cs +++ b/src/Microsoft.OData.Cli/ODataCliFileHandler.cs @@ -102,7 +102,7 @@ public Task EmitContainerPropertyAttributeAsync() /// A bool indicating whether to emit native date and time types or not public Task EmitNativeDateTimeTypesAsync() { - return Task.FromResult(true); + return Task.FromResult(this.project.CheckODataClientVersion()); } /// diff --git a/src/Microsoft.OData.Cli/PackageInstallers/PackageInstallerHelpers.cs b/src/Microsoft.OData.Cli/PackageInstallers/PackageInstallerHelpers.cs index 7bc08099..b73bf9b7 100644 --- a/src/Microsoft.OData.Cli/PackageInstallers/PackageInstallerHelpers.cs +++ b/src/Microsoft.OData.Cli/PackageInstallers/PackageInstallerHelpers.cs @@ -220,7 +220,7 @@ private async Task GetPackageLatestNugetVersionAsync( PackageSearchResource searchResource = await sourceRepository.GetResourceAsync(); string[] targetProjectFrameworks = new[] { projectTargetFramework }; - SearchFilter searchFilter = new SearchFilter(true) // True to include prerelease packages + SearchFilter searchFilter = new SearchFilter(false) { SupportedFrameworks = targetProjectFrameworks }; diff --git a/src/Microsoft.OData.Cli/ProjectHelper.cs b/src/Microsoft.OData.Cli/ProjectHelper.cs index dc330fd6..e295f4ab 100644 --- a/src/Microsoft.OData.Cli/ProjectHelper.cs +++ b/src/Microsoft.OData.Cli/ProjectHelper.cs @@ -143,5 +143,29 @@ internal static string[] GetProjectTargetFrameworks(this Project project) return targetFrameworks; } + + /// + /// Checks if the Microsoft.OData.Client package version in the project is at least 9.0.0. + /// + /// An instance of the loaded . + /// True if the Microsoft.OData.Client version is at least 9.0.0; otherwise, false. + internal static bool CheckODataClientVersion(this Project project) + { + if (project == null) + { + return false; + } + + var version = project.GetItems("PackageReference") + .FirstOrDefault(pr => pr.EvaluatedInclude.Equals("Microsoft.OData.Client", StringComparison.OrdinalIgnoreCase)) + ?.GetMetadataValue("Version"); + + if (version != null && version.Contains('-')) + { + version = version.Substring(0, version.IndexOf("-")); + } + + return Version.TryParse(version, out Version odataClientVersion) && odataClientVersion >= Version.Parse("9.0.0"); + } } } diff --git a/test/Microsoft.OData.Cli.Tests/FileHandling/ODataCliFileHandlerTests.cs b/test/Microsoft.OData.Cli.Tests/FileHandling/ODataCliFileHandlerTests.cs new file mode 100644 index 00000000..797969af --- /dev/null +++ b/test/Microsoft.OData.Cli.Tests/FileHandling/ODataCliFileHandlerTests.cs @@ -0,0 +1,183 @@ +//----------------------------------------------------------------------------------- +// +// Copyright (c) .NET Foundation and Contributors. All rights reserved. +// See License.txt in the project root for license information. +// +//----------------------------------------------------------------------------------- + +using Microsoft.Build.Construction; +using Microsoft.Build.Evaluation; +using Microsoft.Build.Locator; +using Microsoft.OData.CodeGen.Logging; +using Moq; + +namespace Microsoft.OData.Cli.Tests.FileHandling +{ + public class ODataCliFileHandlerTests + { + public ODataCliFileHandlerTests() + { + // Ensure MSBuild is registered for Project API usage + if (!MSBuildLocator.IsRegistered) + { + // pick VS or the .NET SDK automatically + MSBuildLocator.RegisterDefaults(); + } + } + + [Fact] + public async Task EmitNativeDateTimeTypesAsync_ShouldReturnTrue_WhenODataClientVersionIsGreaterThanOrEqualTo9_0_0() + { + // Arrange + var project = CreateProjectWithODataClientVersion("9.0.0"); + var fileHandler = CreateFileHandler(project); + + // Act + var result = await fileHandler.EmitNativeDateTimeTypesAsync(); + + // Assert + Assert.True(result); + } + + [Fact] + public async Task EmitNativeDateTimeTypesAsync_ShouldReturnTrue_WhenODataClientVersionIs9_1_0() + { + // Arrange + var project = CreateProjectWithODataClientVersion("9.1.0"); + var fileHandler = CreateFileHandler(project); + + // Act + var result = await fileHandler.EmitNativeDateTimeTypesAsync(); + + // Assert + Assert.True(result); + } + + [Fact] + public async Task EmitNativeDateTimeTypesAsync_ShouldReturnTrue_WhenODataClientVersionIsPrereleaseAsync() + { + // Arrange + var project = CreateProjectWithODataClientVersion("9.0.0-preview.3"); + var fileHandler = CreateFileHandler(project); + + // Act + var result = await fileHandler.EmitNativeDateTimeTypesAsync(); + + // Assert + Assert.True(result); + } + + [Fact] + public async Task EmitNativeDateTimeTypesAsync_ShouldReturnFalse_WhenODataClientVersionIsLessThan9_0_0() + { + // Arrange + var project = CreateProjectWithODataClientVersion("8.0.0"); + var fileHandler = CreateFileHandler(project); + + // Act + var result = await fileHandler.EmitNativeDateTimeTypesAsync(); + + // Assert + Assert.False(result); + } + + [Fact] + public async Task EmitNativeDateTimeTypesAsync_ShouldReturnFalse_WhenODataClientVersionIs7_6_4() + { + // Arrange + var project = CreateProjectWithODataClientVersion("7.6.4"); + var fileHandler = CreateFileHandler(project); + + // Act + var result = await fileHandler.EmitNativeDateTimeTypesAsync(); + + // Assert + Assert.False(result); + } + + [Fact] + public async Task EmitNativeDateTimeTypesAsync_ShouldReturnFalse_WhenODataClientReferenceNotFound() + { + // Arrange + var project = CreateProjectWithoutODataClient(); + var fileHandler = CreateFileHandler(project); + + // Act + var result = await fileHandler.EmitNativeDateTimeTypesAsync(); + + // Assert + Assert.False(result); + } + + [Fact] + public async Task EmitNativeDateTimeTypesAsync_ShouldReturnFalse_WhenProjectIsNull() + { + // Arrange + var fileHandler = CreateFileHandler(null); + + // Act + var result = await fileHandler.EmitNativeDateTimeTypesAsync(); + + // Assert + Assert.False(result); + } + + [Fact] + public async Task EmitNativeDateTimeTypesAsync_ShouldReturnFalse_WhenVersionCannotBeParsed() + { + // Arrange + var project = CreateProjectWithODataClientVersion("invalid-version"); + var fileHandler = CreateFileHandler(project); + + // Act + var result = await fileHandler.EmitNativeDateTimeTypesAsync(); + + // Assert + Assert.False(result); + } + + private static Project CreateProjectWithODataClientVersion(string version) + { + // Create a .csproj in memory + var pre = ProjectRootElement.Create(); + pre.Sdk = "Microsoft.NET.Sdk"; + + var pg = pre.AddPropertyGroup(); + pg.AddProperty("TargetFramework", "net8.0"); + + var ig = pre.AddItemGroup(); + var pr = ig.AddItem("PackageReference", "Microsoft.OData.Client"); + pr.AddMetadata("Version", version, expressAsAttribute: true); + + // Create an evaluated Project from the XML + var project = new Project(pre); + project.ReevaluateIfNecessary(); + + return project; + } + + private static Project CreateProjectWithoutODataClient() + { + var pre = ProjectRootElement.Create(); + pre.Sdk = "Microsoft.NET.Sdk"; + + var pg = pre.AddPropertyGroup(); + pg.AddProperty("TargetFramework", "net8.0"); + + var ig = pre.AddItemGroup(); + var pr = ig.AddItem("PackageReference", "Newtonsoft.Json"); + pr.AddMetadata("Version", "13.0.1", expressAsAttribute: true); + + var project = new Project(pre); + project.ReevaluateIfNecessary(); + + return project; + } + + private static ODataCliFileHandler CreateFileHandler(Project project) + { + var loggerMock = new Mock(); + return new ODataCliFileHandler(loggerMock.Object, project); + } + } +} diff --git a/test/Microsoft.OData.Cli.Tests/Microsoft.OData.Cli.Tests.csproj b/test/Microsoft.OData.Cli.Tests/Microsoft.OData.Cli.Tests.csproj index 528c0eec..7554c797 100644 --- a/test/Microsoft.OData.Cli.Tests/Microsoft.OData.Cli.Tests.csproj +++ b/test/Microsoft.OData.Cli.Tests/Microsoft.OData.Cli.Tests.csproj @@ -60,6 +60,7 @@ + From 99452c533735c9730e69f672f37b1c512491b518 Mon Sep 17 00:00:00 2001 From: Samuel Wanjohi Date: Mon, 12 Jan 2026 13:17:39 +0300 Subject: [PATCH 10/14] fix failing tests --- .../FileHandling/ODataCliFileHandlerTests.cs | 24 ++++++++++++++----- 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/test/Microsoft.OData.Cli.Tests/FileHandling/ODataCliFileHandlerTests.cs b/test/Microsoft.OData.Cli.Tests/FileHandling/ODataCliFileHandlerTests.cs index 797969af..941fd3a7 100644 --- a/test/Microsoft.OData.Cli.Tests/FileHandling/ODataCliFileHandlerTests.cs +++ b/test/Microsoft.OData.Cli.Tests/FileHandling/ODataCliFileHandlerTests.cs @@ -17,12 +17,7 @@ public class ODataCliFileHandlerTests { public ODataCliFileHandlerTests() { - // Ensure MSBuild is registered for Project API usage - if (!MSBuildLocator.IsRegistered) - { - // pick VS or the .NET SDK automatically - MSBuildLocator.RegisterDefaults(); - } + EnsureMSBuildLoadedIfNot(); } [Fact] @@ -179,5 +174,22 @@ private static ODataCliFileHandler CreateFileHandler(Project project) var loggerMock = new Mock(); return new ODataCliFileHandler(loggerMock.Object, project); } + + private void EnsureMSBuildLoadedIfNot() + { + if (!MSBuildLocator.IsRegistered) + { + try + { + MSBuildLocator.RegisterDefaults(); + } + catch (InvalidOperationException) + { + // MSBuild assemblies were already loaded before registration + // This can happen if another test class already loaded MSBuild types + // Safe to ignore since MSBuild is already available + } + } + } } } From 5f6413af35cf7ba644bf1c765ffd9571ddb48aec Mon Sep 17 00:00:00 2001 From: Samuel Wanjohi Date: Mon, 12 Jan 2026 13:47:01 +0300 Subject: [PATCH 11/14] Do not specify Microsoft.NET.Sdk --- .../FileHandling/ODataCliFileHandlerTests.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/test/Microsoft.OData.Cli.Tests/FileHandling/ODataCliFileHandlerTests.cs b/test/Microsoft.OData.Cli.Tests/FileHandling/ODataCliFileHandlerTests.cs index 941fd3a7..34ab6c1c 100644 --- a/test/Microsoft.OData.Cli.Tests/FileHandling/ODataCliFileHandlerTests.cs +++ b/test/Microsoft.OData.Cli.Tests/FileHandling/ODataCliFileHandlerTests.cs @@ -135,7 +135,6 @@ private static Project CreateProjectWithODataClientVersion(string version) { // Create a .csproj in memory var pre = ProjectRootElement.Create(); - pre.Sdk = "Microsoft.NET.Sdk"; var pg = pre.AddPropertyGroup(); pg.AddProperty("TargetFramework", "net8.0"); @@ -153,8 +152,8 @@ private static Project CreateProjectWithODataClientVersion(string version) private static Project CreateProjectWithoutODataClient() { + // Create a .csproj in memory var pre = ProjectRootElement.Create(); - pre.Sdk = "Microsoft.NET.Sdk"; var pg = pre.AddPropertyGroup(); pg.AddProperty("TargetFramework", "net8.0"); From 2c93c2484ed2c87e48c2fada1f8431ed4d9f6d6f Mon Sep 17 00:00:00 2001 From: Samuel Wanjohi Date: Mon, 12 Jan 2026 14:28:23 +0300 Subject: [PATCH 12/14] add more integration tests --- ...SampleServiceV4WithDateOnlyAndTimeOnly.xml | 59 ++++++++++++ .../ODataCliCodeGenerationTests.cs | 94 +++++++++++++++++++ .../FileHandling/ODataCliFileHandlerTests.cs | 2 +- .../Microsoft.OData.Cli.Tests.csproj | 3 + 4 files changed, 157 insertions(+), 1 deletion(-) create mode 100644 test/Microsoft.OData.Cli.Tests/CodeGeneration/Artifacts/SampleServiceV4WithDateOnlyAndTimeOnly.xml diff --git a/test/Microsoft.OData.Cli.Tests/CodeGeneration/Artifacts/SampleServiceV4WithDateOnlyAndTimeOnly.xml b/test/Microsoft.OData.Cli.Tests/CodeGeneration/Artifacts/SampleServiceV4WithDateOnlyAndTimeOnly.xml new file mode 100644 index 00000000..7f59d510 --- /dev/null +++ b/test/Microsoft.OData.Cli.Tests/CodeGeneration/Artifacts/SampleServiceV4WithDateOnlyAndTimeOnly.xml @@ -0,0 +1,59 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/test/Microsoft.OData.Cli.Tests/CodeGeneration/ODataCliCodeGenerationTests.cs b/test/Microsoft.OData.Cli.Tests/CodeGeneration/ODataCliCodeGenerationTests.cs index 702a4b37..852626c9 100644 --- a/test/Microsoft.OData.Cli.Tests/CodeGeneration/ODataCliCodeGenerationTests.cs +++ b/test/Microsoft.OData.Cli.Tests/CodeGeneration/ODataCliCodeGenerationTests.cs @@ -8,6 +8,8 @@ using System.CommandLine; using System.CommandLine.Parsing; using System.Text.RegularExpressions; +using Microsoft.Build.Evaluation; +using Microsoft.Build.Locator; using Microsoft.OData.CodeGen.Common; namespace Microsoft.OData.Cli.Tests.CodeGeneration @@ -16,6 +18,7 @@ public class ODataCliCodeGenerationTests : IDisposable { private readonly GenerateCommand generateCommand; private readonly string metadataUri = Path.Combine(Environment.CurrentDirectory, "CodeGeneration\\Artifacts\\SampleServiceV4.xml"); + private readonly string metadataUriWithDateOnlyAndTimeOnly = Path.Combine(Environment.CurrentDirectory, "CodeGeneration\\Artifacts\\SampleServiceV4WithDateOnlyAndTimeOnly.xml"); private readonly string lowerCamelCaseMetadataUri = Path.Combine(Environment.CurrentDirectory, "CodeGeneration\\Artifacts\\SampleServiceV4LowerCamelCase.xml"); private readonly string outputDir; @@ -23,6 +26,9 @@ public ODataCliCodeGenerationTests() { this.generateCommand = new GenerateCommand(); this.outputDir = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName()); + + // Ensure MSBuild is registered for Project API usage + EnsureMSBuildLoadedIfNot(); } [Fact] @@ -489,10 +495,49 @@ public void TestCodeGeneratedForServiceNameOption(string commandLine) Regex.Replace(inputCsdlContent, @"\s+", "")); } + [Fact] + public void TestCodeGeneration_WithODataClient9_UsesNativeDateTimeTypes() + { + this.CreateTestProjectInOutputDir("net8.0", "9.0.0-preview.3"); + + var parseResult = this.generateCommand.Parse($"--metadata-uri {this.metadataUriWithDateOnlyAndTimeOnly} --outputdir {this.outputDir}"); + parseResult.Invoke(); + + var referenceProxyFile = Assert.Single(Directory.GetFiles(outputDir, $"{Constants.DefaultReferenceFileName}.cs")); + var generatedCode = File.ReadAllText(referenceProxyFile); + + Assert.NotNull(generatedCode); + Assert.NotEmpty(generatedCode); + + Assert.Contains("public virtual global::System.DateOnly OrderDate", generatedCode); + Assert.Contains("public virtual global::System.TimeOnly OrderTime", generatedCode); + } + + [Fact] + public void TestCodeGeneration_WithODataClientVersionLessThan9_UsesNativeDateTimeTypes() + { + this.CreateTestProjectInOutputDir("net8.0", "8.4.3"); + + var parseResult = this.generateCommand.Parse($"--metadata-uri {this.metadataUriWithDateOnlyAndTimeOnly} --outputdir {this.outputDir}"); + parseResult.Invoke(); + + var referenceProxyFile = Assert.Single(Directory.GetFiles(outputDir, $"{Constants.DefaultReferenceFileName}.cs")); + var generatedCode = File.ReadAllText(referenceProxyFile); + + Assert.NotNull(generatedCode); + Assert.NotEmpty(generatedCode); + + Assert.Contains("public virtual global::Microsoft.OData.Edm.Date OrderDate", generatedCode); + Assert.Contains("public virtual global::Microsoft.OData.Edm.TimeOfDay OrderTime", generatedCode); + } + public void Dispose() { try { + // Unload MSBuild projects before deleting directory + ProjectCollection.GlobalProjectCollection.UnloadAllProjects(); + // Delete the temp directory if possible if (Directory.Exists(outputDir)) { @@ -504,5 +549,54 @@ public void Dispose() // Ignore - Temporary files are eventually clean up } } + + private void CreateTestProjectInOutputDir(string targetFramework, string odataClientVersion) + { + if (!Directory.Exists(this.outputDir)) + { + Directory.CreateDirectory(this.outputDir); + } + + var projectPath = Path.Combine(this.outputDir, "TestProject.csproj"); + + var projectContent = $@" + + + Exe + {targetFramework} + enable + enable + + + + + + + + + + + +"; + + File.WriteAllText(projectPath, projectContent); + } + + private static void EnsureMSBuildLoadedIfNot() + { + if (!MSBuildLocator.IsRegistered) + { + try + { + MSBuildLocator.RegisterDefaults(); + } + catch (InvalidOperationException) + { + // MSBuild assemblies were already loaded before registration + // This can happen if another test class already loaded MSBuild types + // Safe to ignore since MSBuild is already available + } + } + } } } diff --git a/test/Microsoft.OData.Cli.Tests/FileHandling/ODataCliFileHandlerTests.cs b/test/Microsoft.OData.Cli.Tests/FileHandling/ODataCliFileHandlerTests.cs index 34ab6c1c..94c4bdfc 100644 --- a/test/Microsoft.OData.Cli.Tests/FileHandling/ODataCliFileHandlerTests.cs +++ b/test/Microsoft.OData.Cli.Tests/FileHandling/ODataCliFileHandlerTests.cs @@ -174,7 +174,7 @@ private static ODataCliFileHandler CreateFileHandler(Project project) return new ODataCliFileHandler(loggerMock.Object, project); } - private void EnsureMSBuildLoadedIfNot() + private static void EnsureMSBuildLoadedIfNot() { if (!MSBuildLocator.IsRegistered) { diff --git a/test/Microsoft.OData.Cli.Tests/Microsoft.OData.Cli.Tests.csproj b/test/Microsoft.OData.Cli.Tests/Microsoft.OData.Cli.Tests.csproj index 7554c797..171535b6 100644 --- a/test/Microsoft.OData.Cli.Tests/Microsoft.OData.Cli.Tests.csproj +++ b/test/Microsoft.OData.Cli.Tests/Microsoft.OData.Cli.Tests.csproj @@ -74,6 +74,9 @@ + + Always + Always From 5a0b162c22cd1cf96cb55dba2ca1796b6aa14506 Mon Sep 17 00:00:00 2001 From: Samuel Wanjohi Date: Mon, 12 Jan 2026 14:36:54 +0300 Subject: [PATCH 13/14] use IVsPackageInstaller instead of IVsPackageInstaller2 --- .../ConnectedServicePackageInstaller.cs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/ODataConnectedService.Shared/ConnectedServicePackageInstaller.cs b/src/ODataConnectedService.Shared/ConnectedServicePackageInstaller.cs index 50b2a7fe..a8b46563 100644 --- a/src/ODataConnectedService.Shared/ConnectedServicePackageInstaller.cs +++ b/src/ODataConnectedService.Shared/ConnectedServicePackageInstaller.cs @@ -26,7 +26,7 @@ public class ConnectedServicePackageInstaller : IPackageInstaller public ConnectedServiceHandlerContext Context { get; private set; } public Project Project { get; private set; } public IMessageLogger MessageLogger { get; private set; } - public IVsPackageInstaller2 PackageInstaller { get; protected set; } + public IVsPackageInstaller PackageInstaller { get; protected set; } public IVsPackageInstallerServices PackageInstallerServices { get; protected set; } @@ -53,7 +53,7 @@ public void Init() if (componentModel != null) { this.PackageInstallerServices = componentModel.GetService(); - this.PackageInstaller = componentModel.GetService(); + this.PackageInstaller = componentModel.GetService(); } } @@ -70,8 +70,7 @@ public async Task CheckAndInstallNuGetPackageAsync(string packageSource, string { if (!PackageInstallerServices.IsPackageInstalled(this.Project, packageName)) { - //PackageInstaller.InstallPackage(packageSource, this.Project, packageName, (string)null, false); - PackageInstaller.InstallLatestPackage(packageSource, this.Project, packageName, true, false); + PackageInstaller.InstallPackage(packageSource, this.Project, packageName, (string)null, false); await (this.MessageLogger?.WriteMessageAsync(LogMessageCategory.Information, $"Nuget Package \"{packageName}\" for OData client was added.")).ConfigureAwait(false); } From 5182f9a9072284226ad483e89d721b2c271802f5 Mon Sep 17 00:00:00 2001 From: Samuel Wanjohi Date: Mon, 12 Jan 2026 20:07:08 +0300 Subject: [PATCH 14/14] add check to prevent null exception --- src/Microsoft.OData.Cli/ProjectHelper.cs | 7 ++++++- .../ConnectedServiceFileHandler.cs | 4 ++-- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/src/Microsoft.OData.Cli/ProjectHelper.cs b/src/Microsoft.OData.Cli/ProjectHelper.cs index e295f4ab..29e159c0 100644 --- a/src/Microsoft.OData.Cli/ProjectHelper.cs +++ b/src/Microsoft.OData.Cli/ProjectHelper.cs @@ -160,7 +160,12 @@ internal static bool CheckODataClientVersion(this Project project) .FirstOrDefault(pr => pr.EvaluatedInclude.Equals("Microsoft.OData.Client", StringComparison.OrdinalIgnoreCase)) ?.GetMetadataValue("Version"); - if (version != null && version.Contains('-')) + if (string.IsNullOrEmpty(version)) + { + return false; + } + + if (version.Contains('-')) { version = version.Substring(0, version.IndexOf("-")); } diff --git a/src/ODataConnectedService.Shared/ConnectedServiceFileHandler.cs b/src/ODataConnectedService.Shared/ConnectedServiceFileHandler.cs index 8ca4595e..26f7ab16 100644 --- a/src/ODataConnectedService.Shared/ConnectedServiceFileHandler.cs +++ b/src/ODataConnectedService.Shared/ConnectedServiceFileHandler.cs @@ -101,9 +101,9 @@ private Task CheckODataClientVersionAsync(Func versionPredi { return this.threadHelper.RunInUiThreadAsync(() => { - if (this.isOdataClientVersionCached) + if (this.isOdataClientVersionCached && this.odataClientVersion != null) { - return this.odataClientVersion != null && versionPredicate(this.odataClientVersion); + return versionPredicate(this.odataClientVersion); } #pragma warning disable VSTHRD010 // Invoke single-threaded types on Main thread