Repository: NuGet/NuGet.Client
Pull request #6291: Log NU1105 error for badly specified framework
Author: login: Nigusu-Allehu
Created: 2025-02-27T19:38:45Z | Merged: 2025-03-21T21:11:32Z

<!-- DO NOT MODIFY OR DELETE THIS TEMPLATE. IT IS USED IN AUTOMATION. -->

# Bug

<!-- If this is an engineering change or test change only, you do not need an issue. -->
<!-- Find or create an issue in NuGet/Home and paste the full url. -->
<!-- At the maintainers discretion, multiple changes may apply to a single issue, but only when the PRs are all created within a short period of time. -->
Related: https://github.com/NuGet/Home/issues/12943
Fixes: https://github.com/NuGet/Home/issues/14216

## Description

Ensure misconfigured projects (specifically projects with badly specified framework) fail with a coded error (NU1105), improving error clarity and debugging

### Test project
``` xml

<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <OutputType>Library</OutputType>
    <TargetFrameworks>net.6.0;net.8.0</TargetFrameworks>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="Newtonsoft.Json" Version="10.0.3" />
  </ItemGroup>
</Project>
```

### Before
![image](https://github.com/user-attachments/assets/17035f57-dba7-445e-b1fe-93e9d511b878)


### After
![image](https://github.com/user-attachments/assets/2d7bca1f-4fc4-497c-a4ed-e5ad294c320d)


## PR Checklist

- [x] Meaningful title, helpful description and a linked NuGet/Home issue
- [x] Added tests
- [ ] Link to an issue or pull request to update docs if this PR changes settings, environment variables, new feature, etc. https://github.com/NuGet/Home/issues/14149



Diff:
diff --git a/src/NuGet.Core/NuGet.Commands/PublicAPI/net472/PublicAPI.Unshipped.txt b/src/NuGet.Core/NuGet.Commands/PublicAPI/net472/PublicAPI.Unshipped.txt
index db278fc96b9..d9bb4b2a73a 100644
--- a/src/NuGet.Core/NuGet.Commands/PublicAPI/net472/PublicAPI.Unshipped.txt
+++ b/src/NuGet.Core/NuGet.Commands/PublicAPI/net472/PublicAPI.Unshipped.txt
@@ -1,2 +1,3 @@
 #nullable enable
+~static NuGet.Commands.SpecValidationUtility.ValidateDependencySpec(NuGet.ProjectModel.DependencyGraphSpec spec, System.Collections.Generic.HashSet<string> projectsToSkip, NuGet.Common.ILogger logger) -> void
 ~static NuGet.Commands.MSBuildRestoreUtility.GetRestoreAuditProperties(NuGet.Commands.IMSBuildItem specItem, System.Collections.Generic.IEnumerable<NuGet.Commands.IMSBuildItem> allItems) -> NuGet.ProjectModel.RestoreAuditProperties
diff --git a/src/NuGet.Core/NuGet.Commands/PublicAPI/net8.0/PublicAPI.Unshipped.txt b/src/NuGet.Core/NuGet.Commands/PublicAPI/net8.0/PublicAPI.Unshipped.txt
index db278fc96b9..d9bb4b2a73a 100644
--- a/src/NuGet.Core/NuGet.Commands/PublicAPI/net8.0/PublicAPI.Unshipped.txt
+++ b/src/NuGet.Core/NuGet.Commands/PublicAPI/net8.0/PublicAPI.Unshipped.txt
@@ -1,2 +1,3 @@
 #nullable enable
+~static NuGet.Commands.SpecValidationUtility.ValidateDependencySpec(NuGet.ProjectModel.DependencyGraphSpec spec, System.Collections.Generic.HashSet<string> projectsToSkip, NuGet.Common.ILogger logger) -> void
 ~static NuGet.Commands.MSBuildRestoreUtility.GetRestoreAuditProperties(NuGet.Commands.IMSBuildItem specItem, System.Collections.Generic.IEnumerable<NuGet.Commands.IMSBuildItem> allItems) -> NuGet.ProjectModel.RestoreAuditProperties
diff --git a/src/NuGet.Core/NuGet.Commands/PublicAPI/netstandard2.0/PublicAPI.Unshipped.txt b/src/NuGet.Core/NuGet.Commands/PublicAPI/netstandard2.0/PublicAPI.Unshipped.txt
index db278fc96b9..d9bb4b2a73a 100644
--- a/src/NuGet.Core/NuGet.Commands/PublicAPI/netstandard2.0/PublicAPI.Unshipped.txt
+++ b/src/NuGet.Core/NuGet.Commands/PublicAPI/netstandard2.0/PublicAPI.Unshipped.txt
@@ -1,2 +1,3 @@
 #nullable enable
+~static NuGet.Commands.SpecValidationUtility.ValidateDependencySpec(NuGet.ProjectModel.DependencyGraphSpec spec, System.Collections.Generic.HashSet<string> projectsToSkip, NuGet.Common.ILogger logger) -> void
 ~static NuGet.Commands.MSBuildRestoreUtility.GetRestoreAuditProperties(NuGet.Commands.IMSBuildItem specItem, System.Collections.Generic.IEnumerable<NuGet.Commands.IMSBuildItem> allItems) -> NuGet.ProjectModel.RestoreAuditProperties
diff --git a/src/NuGet.Core/NuGet.Commands/RestoreCommand/RequestFactory/DependencyGraphSpecRequestProvider.cs b/src/NuGet.Core/NuGet.Commands/RestoreCommand/RequestFactory/DependencyGraphSpecRequestProvider.cs
index 0f9dc537e73..df448220a41 100644
--- a/src/NuGet.Core/NuGet.Commands/RestoreCommand/RequestFactory/DependencyGraphSpecRequestProvider.cs
+++ b/src/NuGet.Core/NuGet.Commands/RestoreCommand/RequestFactory/DependencyGraphSpecRequestProvider.cs
@@ -76,7 +76,7 @@ private IReadOnlyList<RestoreSummaryRequest> GetRequestsFromItems(RestoreArgs re
                     projectsWithErrors.Add(projectPath);
                 }
             }
-            SpecValidationUtility.ValidateDependencySpec(dgFile, projectsWithErrors);
+            SpecValidationUtility.ValidateDependencySpec(dgFile, projectsWithErrors, restoreContext.Log);
 
             // Create requests
             var requests = new ConcurrentBag<RestoreSummaryRequest>();
diff --git a/src/NuGet.Core/NuGet.Commands/RestoreCommand/Utility/SpecValidationUtility.cs b/src/NuGet.Core/NuGet.Commands/RestoreCommand/Utility/SpecValidationUtility.cs
index 75d86fe4fb1..c2d71fb9d8a 100644
--- a/src/NuGet.Core/NuGet.Commands/RestoreCommand/Utility/SpecValidationUtility.cs
+++ b/src/NuGet.Core/NuGet.Commands/RestoreCommand/Utility/SpecValidationUtility.cs
@@ -5,6 +5,7 @@
 using System.Collections.Generic;
 using System.Globalization;
 using System.Linq;
+using NuGet.Common;
 using NuGet.Frameworks;
 using NuGet.LibraryModel;
 using NuGet.ProjectModel;
@@ -27,6 +28,13 @@ public static void ValidateDependencySpec(DependencyGraphSpec spec)
         /// </summary>
         public static void ValidateDependencySpec(DependencyGraphSpec spec, HashSet<string> projectsToSkip)
         {
+            ValidateDependencySpec(spec, projectsToSkip, NullLogger.Instance);
+        }
+
+        public static void ValidateDependencySpec(DependencyGraphSpec spec, HashSet<string> projectsToSkip, ILogger logger)
+        {
+            logger ??= NullLogger.Instance;
+
             if (spec == null)
             {
                 throw new ArgumentNullException(nameof(spec));
@@ -39,7 +47,7 @@ public static void ValidateDependencySpec(DependencyGraphSpec spec, HashSet<stri
                 {
                     if (!projectsToSkip.Contains(projectSpec.FilePath))
                     {
-                        ValidateProjectSpec(projectSpec);
+                        ValidateProjectSpec(projectSpec, logger);
                     }
                 }
 
@@ -77,6 +85,11 @@ public static void ValidateDependencySpec(DependencyGraphSpec spec, HashSet<stri
         }
 
         public static void ValidateProjectSpec(PackageSpec spec)
+        {
+            ValidateProjectSpec(spec, NullLogger.Instance);
+        }
+
+        private static void ValidateProjectSpec(PackageSpec spec, ILogger logger)
         {
             if (spec == null)
             {
@@ -123,15 +136,15 @@ public static void ValidateProjectSpec(PackageSpec spec)
                 switch (projectStyle)
                 {
                     case ProjectStyle.PackageReference:
-                        ValidateProjectSpecPackageReference(spec, files);
+                        ValidateProjectSpecPackageReference(spec, files, logger);
                         break;
 
                     case ProjectStyle.DotnetToolReference:
-                        ValidateProjectSpecPackageReference(spec, files);
+                        ValidateProjectSpecPackageReference(spec, files, logger);
                         break;
 
                     case ProjectStyle.ProjectJson:
-                        ValidateProjectSpecUAP(spec, files);
+                        ValidateProjectSpecUAP(spec, files, logger);
                         break;
 
                     default:
@@ -141,44 +154,56 @@ public static void ValidateProjectSpec(PackageSpec spec)
             }
         }
 
-        private static void ValidateFrameworks(PackageSpec spec, IEnumerable<string> files)
+        private static void ValidateFrameworks(PackageSpec spec, IEnumerable<string> files, ILogger logger)
         {
-            var frameworks = spec.TargetFrameworks.Select(f => f.FrameworkName).ToArray();
+            if (spec.TargetFrameworks == null)
+            {
+                throw RestoreSpecException.Create(Strings.SpecValidationNoFrameworks, files);
+            }
 
-            // Verify frameworks are valid
-            foreach (var framework in frameworks.Where(f => !f.IsSpecificFramework))
+            bool hasInvalidFrameworks = false;
+            List<NuGetFramework> frameworkNames = new List<NuGetFramework>(spec.TargetFrameworks.Count);
+
+            foreach (var framework in spec.TargetFrameworks)
             {
-                var message = string.Format(
-                    CultureInfo.CurrentCulture,
-                    Strings.SpecValidationInvalidFramework,
-                    framework.GetShortFolderName());
+                frameworkNames.Add(framework.FrameworkName);
 
-                throw RestoreSpecException.Create(message, files);
+                if (!framework.FrameworkName.IsSpecificFramework)
+                {
+                    hasInvalidFrameworks |= true;
+                    var message = string.Format(CultureInfo.CurrentCulture, Strings.SpecValidationInvalidFramework, framework.TargetAlias);
+                    logger.Log(new RestoreLogMessage(LogLevel.Error, NuGetLogCode.NU1105, message) { FilePath = spec.FilePath, ProjectPath = spec.FilePath });
+                }
+            }
+
+            if (hasInvalidFrameworks)
+            {
+                throw RestoreSpecException.Create(string.Format(CultureInfo.CurrentCulture, Strings.Invalid_Framework), files);
             }
 
             // Must have at least 1 framework
-            if (frameworks.Length < 1)
+            if (frameworkNames.Count < 1)
             {
                 throw RestoreSpecException.Create(Strings.SpecValidationNoFrameworks, files);
             }
 
             // Duplicate frameworks may not exist
             // Change in ATF should *not* affect our duplicate check, so we use the full framework comparer.
-            if (frameworks.Length != frameworks.Distinct(NuGetFrameworkFullComparer.Instance).Count())
+            if (frameworkNames.Count != frameworkNames.Distinct(NuGetFrameworkFullComparer.Instance).Count())
             {
                 var message = string.Format(
                     CultureInfo.CurrentCulture,
                     Strings.SpecValidationDuplicateFrameworks,
-                    string.Join(", ", frameworks.Select(f => f.GetShortFolderName())));
+                    string.Join(", ", frameworkNames.Select(f => f.GetShortFolderName())));
 
                 throw RestoreSpecException.Create(message, files);
             }
         }
 
-        private static void ValidateProjectSpecPackageReference(PackageSpec spec, IEnumerable<string> files)
+        private static void ValidateProjectSpecPackageReference(PackageSpec spec, IEnumerable<string> files, ILogger logger)
         {
             // Verify frameworks
-            ValidateFrameworks(spec, files);
+            ValidateFrameworks(spec, files, logger);
 
             // NETCore may not specify a project.json file
             if (!string.IsNullOrEmpty(spec.RestoreMetadata.ProjectJsonPath))
@@ -234,10 +259,10 @@ private static void ValidateProjectSpecPackageReference(PackageSpec spec, IEnume
             }
         }
 
-        private static void ValidateProjectSpecUAP(PackageSpec spec, IEnumerable<string> files)
+        private static void ValidateProjectSpecUAP(PackageSpec spec, IEnumerable<string> files, ILogger logger)
         {
             // Verify frameworks
-            ValidateFrameworks(spec, files);
+            ValidateFrameworks(spec, files, logger);
 
             // UAP may contain only 1 framework
             if (spec.TargetFrameworks.Count != 1)
diff --git a/src/NuGet.Core/NuGet.Commands/Strings.Designer.cs b/src/NuGet.Core/NuGet.Commands/Strings.Designer.cs
index 3ba39f756a8..5401b7e4174 100644
--- a/src/NuGet.Core/NuGet.Commands/Strings.Designer.cs
+++ b/src/NuGet.Core/NuGet.Commands/Strings.Designer.cs
@@ -1041,6 +1041,15 @@ internal static string Invalid_AttributeValue {
             }
         }
         
+        /// <summary>
+        ///   Looks up a localized string similar to One or more invalid frameworks were detected..
+        /// </summary>
+        internal static string Invalid_Framework {
+            get {
+                return ResourceManager.GetString("Invalid_Framework", resourceCulture);
+            }
+        }
+        
         /// <summary>
         ///   Looks up a localized string similar to Invalid restore input. {0}.
         /// </summary>
diff --git a/src/NuGet.Core/NuGet.Commands/Strings.resx b/src/NuGet.Core/NuGet.Commands/Strings.resx
index a56375623d5..6155f2af912 100644
--- a/src/NuGet.Core/NuGet.Commands/Strings.resx
+++ b/src/NuGet.Core/NuGet.Commands/Strings.resx
@@ -1139,4 +1139,7 @@ NuGet requires HTTPS sources. Refer to https://aka.ms/nuget-https-everywhere for
     <value>A ProjectReference cannot be pruned, {0}.</value>
     <comment>0 - project reference</comment>
   </data>
+  <data name="Invalid_Framework" xml:space="preserve">
+    <value>One or more invalid frameworks were detected.</value>
+  </data>
 </root>
\ No newline at end of file
diff --git a/test/NuGet.Core.Tests/NuGet.Commands.Test/SpecValidationUtilityTests.cs b/test/NuGet.Core.Tests/NuGet.Commands.Test/SpecValidationUtilityTests.cs
index 5b71031819d..45919db1965 100644
--- a/test/NuGet.Core.Tests/NuGet.Commands.Test/SpecValidationUtilityTests.cs
+++ b/test/NuGet.Core.Tests/NuGet.Commands.Test/SpecValidationUtilityTests.cs
@@ -1,9 +1,13 @@
 // Copyright (c) .NET Foundation. All rights reserved.
 // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
 
+using System;
 using System.Collections.Generic;
 using System.IO;
 using System.Linq;
+using FluentAssertions;
+using Moq;
+using NuGet.Common;
 using NuGet.Frameworks;
 using NuGet.LibraryModel;
 using NuGet.ProjectModel;
@@ -53,6 +57,10 @@ public void SpecValidationUtility_VerifyFrameworks_Unsupported()
             // Arrange
             var spec = new DependencyGraphSpec();
             spec.AddRestore("a");
+            var errors = new List<NuGetLogCode>();
+            var mockLogger = new Mock<ILogger>();
+            mockLogger.Setup(l => l.Log(It.IsAny<ILogMessage>()))
+                .Callback((ILogMessage message) => { errors.Add(message.Code); });
 
             var targetFramework = new TargetFrameworkInformation()
             {
@@ -72,8 +80,12 @@ public void SpecValidationUtility_VerifyFrameworks_Unsupported()
 
             spec.AddProject(project);
 
-            // Act && Assert
-            AssertError(spec, "Invalid target framework");
+            // Act
+            Action act = () => SpecValidationUtility.ValidateDependencySpec(spec, new HashSet<string>(), mockLogger.Object);
+
+            // Assert
+            act.Should().Throw<RestoreSpecException>().Where(e => e.Message.Contains(project.FilePath));
+            errors.Should().Contain(NuGetLogCode.NU1105);
         }
 
         [Fact]


Reviews:
user: login: jgonz120
body: lgtm, not my area of expertise. Could you provide a screenshot of what the command prompt showed before your change? 
state: DISMISSED
author_association: CONTRIBUTOR
submitted_at: 2025-02-27T22:23:06Z
user: login: jeffkl
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-02-27T22:28:16Z
user: login: Nigusu-Allehu
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-02-27T22:35:11Z
user: login: jeffkl
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-02-27T22:46:13Z
user: login: Nigusu-Allehu
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-02-27T22:53:12Z
user: login: jeffkl
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-02-27T22:57:45Z
user: login: Nigusu-Allehu
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-02-27T23:20:27Z
user: login: nkolev92
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-02-28T01:02:36Z
user: login: donnie-msft
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-02-28T20:00:10Z
user: login: Nigusu-Allehu
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-02-28T23:00:57Z
user: login: nkolev92
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-01T01:11:49Z
user: login: jeffkl
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-04T23:18:30Z
user: login: Nigusu-Allehu
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-05T00:35:24Z
user: login: nkolev92
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-05T01:11:59Z
user: login: Nigusu-Allehu
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-05T17:35:12Z
user: login: nkolev92
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-05T17:55:58Z
user: login: nkolev92
state: DISMISSED
author_association: MEMBER
submitted_at: 2025-03-05T19:50:15Z
user: login: Nigusu-Allehu
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-05T19:58:59Z
user: login: nkolev92
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-05T20:13:36Z
user: login: nkolev92
state: DISMISSED
author_association: MEMBER
submitted_at: 2025-03-05T22:28:44Z
user: login: donnie-msft
body: Thx for addressing my earlier comments!
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-06T00:01:27Z
user: login: donnie-msft
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-06T18:37:05Z
user: login: Nigusu-Allehu
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-06T18:49:15Z
user: login: jeffkl
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-06T18:57:24Z
user: login: donnie-msft
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-06T19:17:55Z
user: login: Nigusu-Allehu
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-06T21:23:24Z
user: login: Nigusu-Allehu
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-06T21:53:43Z
user: login: donnie-msft
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-08T01:08:52Z
user: login: Nigusu-Allehu
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-10T16:21:25Z
user: login: jeffkl
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-11T22:13:21Z
user: login: Nigusu-Allehu
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-13T18:33:13Z
user: login: jeffkl
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-13T19:03:18Z
user: login: nkolev92
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-13T19:06:23Z
user: login: Nigusu-Allehu
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-17T23:33:30Z
user: login: nkolev92
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-18T20:20:28Z
user: login: jeffkl
body: That error is sooooo much better!
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-03-21T21:11:31Z

Review comments:
diff_hunk: @@ -153,7 +164,9 @@ private static void ValidateFrameworks(PackageSpec spec, IEnumerable<string> fil
                     Strings.SpecValidationInvalidFramework,
                     framework.GetShortFolderName());
 
-                throw RestoreSpecException.Create(message, files);
+                logger.Log(new RestoreLogMessage(LogLevel.Error, NuGetLogCode.NU1105, message));
path: src/NuGet.Core/NuGet.Commands/RestoreCommand/Utility/SpecValidationUtility.cs
user: login: jeffkl
body: Is it in scope of this change to log an error for each invalid target framework and then throw after the `foreach` if any were invalid?  Not a huge improvement but would be better to tell the user about all invalid frameworks instead of just the first.
created_at: 2025-02-27T22:28:08Z
author_association: CONTRIBUTOR
diff_hunk: @@ -153,7 +164,9 @@ private static void ValidateFrameworks(PackageSpec spec, IEnumerable<string> fil
                     Strings.SpecValidationInvalidFramework,
                     framework.GetShortFolderName());
 
-                throw RestoreSpecException.Create(message, files);
+                logger.Log(new RestoreLogMessage(LogLevel.Error, NuGetLogCode.NU1105, message));
path: src/NuGet.Core/NuGet.Commands/RestoreCommand/Utility/SpecValidationUtility.cs
user: login: Nigusu-Allehu
body: I was thinking about that actually. However, `framework.GetShortFolderName()` returns "unsupported" instead of the name of the specific framework the user entered. Is there another way to get the name of the fmw name the user entered? I have tried `framework.Framework` as well. 

If we don't have the specific framework name and we try to aggregate the list of all the problematic fmws the error would end up looking like

> Invalid framework : "unsupported", "unsupported", "unsupported"
created_at: 2025-02-27T22:35:11Z
author_association: MEMBER
diff_hunk: @@ -153,7 +164,9 @@ private static void ValidateFrameworks(PackageSpec spec, IEnumerable<string> fil
                     Strings.SpecValidationInvalidFramework,
                     framework.GetShortFolderName());
 
-                throw RestoreSpecException.Create(message, files);
+                logger.Log(new RestoreLogMessage(LogLevel.Error, NuGetLogCode.NU1105, message));
path: src/NuGet.Core/NuGet.Commands/RestoreCommand/Utility/SpecValidationUtility.cs
user: login: jeffkl
body: We could do something like this:
```c#
Hashset<string> invalidFrameworks = new Hashset<string>();

 // Verify frameworks are valid
foreach (var framework in frameworks.Where(f => !f.IsSpecificFramework))
{
    string frameworkShortFolderName = framework.GetShortFolderName();

    if (!invalidFrameworks.Add(frameworkShortFolderName))
    {
        continue;
    }

    var message = string.Format(CultureInfo.CurrentCulture, Strings.SpecValidationInvalidFramework,frameworkShortFolderName);

    logger.Log(new RestoreLogMessage(LogLevel.Error, NuGetLogCode.NU1105, message));
}

if (invalidFrameworks.Count > 0)
{
    throw RestoreSpecException.Create("", files);
}
```
created_at: 2025-02-27T22:46:13Z
author_association: CONTRIBUTOR
diff_hunk: @@ -153,7 +164,9 @@ private static void ValidateFrameworks(PackageSpec spec, IEnumerable<string> fil
                     Strings.SpecValidationInvalidFramework,
                     framework.GetShortFolderName());
 
-                throw RestoreSpecException.Create(message, files);
+                logger.Log(new RestoreLogMessage(LogLevel.Error, NuGetLogCode.NU1105, message));
path: src/NuGet.Core/NuGet.Commands/RestoreCommand/Utility/SpecValidationUtility.cs
user: login: Nigusu-Allehu
body: The issue is that GetShortFolderName() returns "unsupported" instead of the actual framework name that the user provided. This makes it difficult to provide meaningful error messages when logging multiple invalid frameworks.

If we want to log all invalid frameworks before throwing, we need a way to retrieve the exact framework string the user specified. framework.Framework doesn't seem to provide that either.

Do we have another way to access the raw user input for the framework? 

If we log each invalid framework individually, the output might look like:

```
NU1105: Invalid framework: "unsupported"
NU1105: Invalid framework: "unsupported"
NU1105: Invalid framework: "unsupported"
```
created_at: 2025-02-27T22:53:12Z
author_association: MEMBER
diff_hunk: @@ -153,7 +164,9 @@ private static void ValidateFrameworks(PackageSpec spec, IEnumerable<string> fil
                     Strings.SpecValidationInvalidFramework,
                     framework.GetShortFolderName());
 
-                throw RestoreSpecException.Create(message, files);
+                logger.Log(new RestoreLogMessage(LogLevel.Error, NuGetLogCode.NU1105, message));
path: src/NuGet.Core/NuGet.Commands/RestoreCommand/Utility/SpecValidationUtility.cs
user: login: jeffkl
body: My code is using a hashset to at least de-dupe entries. I don't see anywhere in the code where `NuGetFramework.Parse()` stores the original string. 
created_at: 2025-02-27T22:57:45Z
author_association: CONTRIBUTOR
diff_hunk: @@ -153,7 +164,9 @@ private static void ValidateFrameworks(PackageSpec spec, IEnumerable<string> fil
                     Strings.SpecValidationInvalidFramework,
                     framework.GetShortFolderName());
 
-                throw RestoreSpecException.Create(message, files);
+                logger.Log(new RestoreLogMessage(LogLevel.Error, NuGetLogCode.NU1105, message));
path: src/NuGet.Core/NuGet.Commands/RestoreCommand/Utility/SpecValidationUtility.cs
user: login: Nigusu-Allehu
body: Ahh I see. If we had the original string, that would have been really handy here. Without it, I don't think we should iterate through all the frameworks. We won't be able to provide the user an additional information
created_at: 2025-02-27T23:20:27Z
author_association: MEMBER
diff_hunk: @@ -153,7 +164,9 @@ private static void ValidateFrameworks(PackageSpec spec, IEnumerable<string> fil
                     Strings.SpecValidationInvalidFramework,
                     framework.GetShortFolderName());
 
-                throw RestoreSpecException.Create(message, files);
+                logger.Log(new RestoreLogMessage(LogLevel.Error, NuGetLogCode.NU1105, message));
path: src/NuGet.Core/NuGet.Commands/RestoreCommand/Utility/SpecValidationUtility.cs
user: login: nkolev92
body: What does `TargetAlias` contain? Maybe we can report that?

fwiw, https://github.com/NuGet/Home/issues/8174 exists too.

On another note, this doesn't lead to an assets file with an error, so it'd still fail the whole operation.

I'm wondering if we can replicate what VS does?
That might be a bigger piece of work, but worth checking.  
created_at: 2025-02-28T01:02:35Z
author_association: MEMBER
diff_hunk: @@ -72,8 +80,19 @@ public void SpecValidationUtility_VerifyFrameworks_Unsupported()
 
             spec.AddProject(project);
 
-            // Act && Assert
-            AssertError(spec, "Invalid target framework");
+            // Act
+            try
+            {
+                SpecValidationUtility.ValidateDependencySpec(spec, new HashSet<string>(), mockLogger.Object);
+            }
+            catch (Exception ex)
+            {
+                exception = ex.Message;
+            }
+
+            // Assert
+            Assert.Contains(NuGetLogCode.NU1105.ToString(), errors);
path: test/NuGet.Core.Tests/NuGet.Commands.Test/SpecValidationUtilityTests.cs
user: login: donnie-msft
body: Nice test!
Use FluentAssertions ?
created_at: 2025-02-28T19:50:54Z
author_association: CONTRIBUTOR
diff_hunk: @@ -53,6 +56,11 @@ public void SpecValidationUtility_VerifyFrameworks_Unsupported()
             // Arrange
             var spec = new DependencyGraphSpec();
             spec.AddRestore("a");
+            var errors = new List<string>();
path: test/NuGet.Core.Tests/NuGet.Commands.Test/SpecValidationUtilityTests.cs
user: login: donnie-msft
body: Just make it a List of error codes and avoid the ToString and Contains overhead?
created_at: 2025-02-28T19:51:55Z
author_association: CONTRIBUTOR
diff_hunk: @@ -141,44 +152,51 @@ public static void ValidateProjectSpec(PackageSpec spec)
             }
         }
 
-        private static void ValidateFrameworks(PackageSpec spec, IEnumerable<string> files)
+        private static void ValidateFrameworks(PackageSpec spec, IEnumerable<string> files, ILogger logger)
         {
-            var frameworks = spec.TargetFrameworks.Select(f => f.FrameworkName).ToArray();
+            var frameworkNames = spec.TargetFrameworks.Select(f => f.FrameworkName).ToArray();
+            HashSet<string> invalidFrameworks = new HashSet<string>();
 
             // Verify frameworks are valid
-            foreach (var framework in frameworks.Where(f => !f.IsSpecificFramework))
+            foreach (var framework in spec.TargetFrameworks.Where(f => !f.FrameworkName.IsSpecificFramework))
             {
-                var message = string.Format(
-                    CultureInfo.CurrentCulture,
-                    Strings.SpecValidationInvalidFramework,
-                    framework.GetShortFolderName());
+                if (!invalidFrameworks.Add(framework.TargetAlias))
+                {
+                    continue;
+                }
 
-                throw RestoreSpecException.Create(message, files);
+                var message = string.Format(CultureInfo.CurrentCulture, Strings.SpecValidationInvalidFramework, framework.TargetAlias);
+                logger.Log(new RestoreLogMessage(LogLevel.Error, NuGetLogCode.NU1105, message));
+            }
+
+            if (invalidFrameworks.Count > 0)
+            {
+                throw RestoreSpecException.Create("", files);
path: src/NuGet.Core/NuGet.Commands/RestoreCommand/Utility/SpecValidationUtility.cs
user: login: donnie-msft
body: I'm assuming the "" will be replaced with some new message that says in a generic way that one or more frameworks were invalid?
created_at: 2025-02-28T19:57:18Z
author_association: CONTRIBUTOR
diff_hunk: @@ -26,6 +27,11 @@ public static void ValidateDependencySpec(DependencyGraphSpec spec)
         /// Validate a dg file. This will throw a RestoreSpecException if there are errors.
         /// </summary>
         public static void ValidateDependencySpec(DependencyGraphSpec spec, HashSet<string> projectsToSkip)
+        {
+            ValidateDependencySpec(spec, projectsToSkip, new NullLogger());
path: src/NuGet.Core/NuGet.Commands/RestoreCommand/Utility/SpecValidationUtility.cs
user: login: donnie-msft
body: I believe the singleton could be used here.

```suggestion
            ValidateDependencySpec(spec, projectsToSkip, NullLogger.Instance);
```
created_at: 2025-02-28T19:59:27Z
author_association: CONTRIBUTOR
diff_hunk: @@ -77,6 +83,11 @@ public static void ValidateDependencySpec(DependencyGraphSpec spec, HashSet<stri
         }
 
         public static void ValidateProjectSpec(PackageSpec spec)
+        {
+            ValidateProjectSpec(spec, new NullLogger());
path: src/NuGet.Core/NuGet.Commands/RestoreCommand/Utility/SpecValidationUtility.cs
user: login: donnie-msft
body: Singleton here as well
created_at: 2025-02-28T19:59:43Z
author_association: CONTRIBUTOR
diff_hunk: @@ -153,7 +164,9 @@ private static void ValidateFrameworks(PackageSpec spec, IEnumerable<string> fil
                     Strings.SpecValidationInvalidFramework,
                     framework.GetShortFolderName());
 
-                throw RestoreSpecException.Create(message, files);
+                logger.Log(new RestoreLogMessage(LogLevel.Error, NuGetLogCode.NU1105, message));
path: src/NuGet.Core/NuGet.Commands/RestoreCommand/Utility/SpecValidationUtility.cs
user: login: Nigusu-Allehu
body: I was able to use `TargetAlias`. Thank you! I like the idea of the logging the errors in an assets file. How about I create a separate PR for that? what do you think?
created_at: 2025-02-28T23:00:56Z
author_association: MEMBER
diff_hunk: @@ -153,7 +164,9 @@ private static void ValidateFrameworks(PackageSpec spec, IEnumerable<string> fil
                     Strings.SpecValidationInvalidFramework,
                     framework.GetShortFolderName());
 
-                throw RestoreSpecException.Create(message, files);
+                logger.Log(new RestoreLogMessage(LogLevel.Error, NuGetLogCode.NU1105, message));
path: src/NuGet.Core/NuGet.Commands/RestoreCommand/Utility/SpecValidationUtility.cs
user: login: nkolev92
body: I think when I created the issue originally, the scope I had in mind was fixing it all the way. 
In particular, I mention that it's being treated as an internal NuGet issue, but realistically, it's a misconfigured project. 

We can probably re-use the same issue and just say "related". I feel like that improves things by quite a lot, but doesn't necessarily solve the problem permanently.
created_at: 2025-03-01T01:11:49Z
author_association: MEMBER
diff_hunk: @@ -141,44 +152,51 @@ public static void ValidateProjectSpec(PackageSpec spec)
             }
         }
 
-        private static void ValidateFrameworks(PackageSpec spec, IEnumerable<string> files)
+        private static void ValidateFrameworks(PackageSpec spec, IEnumerable<string> files, ILogger logger)
         {
-            var frameworks = spec.TargetFrameworks.Select(f => f.FrameworkName).ToArray();
+            var frameworkNames = spec.TargetFrameworks.Select(f => f.FrameworkName).ToArray();
+            HashSet<string> invalidFrameworks = new HashSet<string>();
 
             // Verify frameworks are valid
-            foreach (var framework in frameworks.Where(f => !f.IsSpecificFramework))
+            foreach (var framework in spec.TargetFrameworks.Where(f => !f.FrameworkName.IsSpecificFramework))
             {
-                var message = string.Format(
-                    CultureInfo.CurrentCulture,
-                    Strings.SpecValidationInvalidFramework,
-                    framework.GetShortFolderName());
+                if (!invalidFrameworks.Add(framework.TargetAlias))
path: src/NuGet.Core/NuGet.Commands/RestoreCommand/Utility/SpecValidationUtility.cs
user: login: jeffkl
body: Only allocate `invalidFrameworks` if needed
created_at: 2025-03-04T23:18:30Z
author_association: CONTRIBUTOR
diff_hunk: @@ -141,44 +152,51 @@ public static void ValidateProjectSpec(PackageSpec spec)
             }
         }
 
-        private static void ValidateFrameworks(PackageSpec spec, IEnumerable<string> files)
+        private static void ValidateFrameworks(PackageSpec spec, IEnumerable<string> files, ILogger logger)
         {
-            var frameworks = spec.TargetFrameworks.Select(f => f.FrameworkName).ToArray();
+            var frameworkNames = spec.TargetFrameworks.Select(f => f.FrameworkName).ToArray();
+            HashSet<string> invalidFrameworks = new HashSet<string>();
 
             // Verify frameworks are valid
-            foreach (var framework in frameworks.Where(f => !f.IsSpecificFramework))
+            foreach (var framework in spec.TargetFrameworks.Where(f => !f.FrameworkName.IsSpecificFramework))
             {
-                var message = string.Format(
-                    CultureInfo.CurrentCulture,
-                    Strings.SpecValidationInvalidFramework,
-                    framework.GetShortFolderName());
+                if (!invalidFrameworks.Add(framework.TargetAlias))
+                {
+                    continue;
+                }
 
-                throw RestoreSpecException.Create(message, files);
+                var message = string.Format(CultureInfo.CurrentCulture, Strings.SpecValidationInvalidFramework, framework.TargetAlias);
+                logger.Log(new RestoreLogMessage(LogLevel.Error, NuGetLogCode.NU1105, message));
+            }
+
+            if (invalidFrameworks.Count > 0)
+            {
+                throw RestoreSpecException.Create("", files);
path: src/NuGet.Core/NuGet.Commands/RestoreCommand/Utility/SpecValidationUtility.cs
user: login: Nigusu-Allehu
body: This was on purpose. The logging of the error is done using an ilogger in a previous line.
The exception would look like
> error : Invalid restore input. Input files: N:\trash\p7\p7\p7.csproj. 
created_at: 2025-03-05T00:35:24Z
author_association: MEMBER
diff_hunk: @@ -141,44 +152,56 @@ public static void ValidateProjectSpec(PackageSpec spec)
             }
         }
 
-        private static void ValidateFrameworks(PackageSpec spec, IEnumerable<string> files)
+        private static void ValidateFrameworks(PackageSpec spec, IEnumerable<string> files, ILogger logger)
         {
-            var frameworks = spec.TargetFrameworks.Select(f => f.FrameworkName).ToArray();
+            var frameworkNames = spec.TargetFrameworks.Select(f => f.FrameworkName).ToArray();
+            HashSet<string> invalidFrameworks = null;
 
             // Verify frameworks are valid
-            foreach (var framework in frameworks.Where(f => !f.IsSpecificFramework))
+            foreach (var framework in spec.TargetFrameworks.Where(f => !f.FrameworkName.IsSpecificFramework))
             {
-                var message = string.Format(
-                    CultureInfo.CurrentCulture,
-                    Strings.SpecValidationInvalidFramework,
-                    framework.GetShortFolderName());
+                if (invalidFrameworks == null)
path: src/NuGet.Core/NuGet.Commands/RestoreCommand/Utility/SpecValidationUtility.cs
user: login: nkolev92
body: You can use null coalescing to write this on a single line.
created_at: 2025-03-05T01:10:40Z
author_association: MEMBER
diff_hunk: @@ -141,44 +152,56 @@ public static void ValidateProjectSpec(PackageSpec spec)
             }
         }
 
-        private static void ValidateFrameworks(PackageSpec spec, IEnumerable<string> files)
+        private static void ValidateFrameworks(PackageSpec spec, IEnumerable<string> files, ILogger logger)
         {
-            var frameworks = spec.TargetFrameworks.Select(f => f.FrameworkName).ToArray();
+            var frameworkNames = spec.TargetFrameworks.Select(f => f.FrameworkName).ToArray();
+            HashSet<string> invalidFrameworks = null;
 
             // Verify frameworks are valid
-            foreach (var framework in frameworks.Where(f => !f.IsSpecificFramework))
+            foreach (var framework in spec.TargetFrameworks.Where(f => !f.FrameworkName.IsSpecificFramework))
             {
-                var message = string.Format(
-                    CultureInfo.CurrentCulture,
-                    Strings.SpecValidationInvalidFramework,
-                    framework.GetShortFolderName());
+                if (invalidFrameworks == null)
path: src/NuGet.Core/NuGet.Commands/RestoreCommand/Utility/SpecValidationUtility.cs
user: login: nkolev92
body: I just now realized that you don't need this hashset. 

TargetAlias is always gonna be unique.
created_at: 2025-03-05T01:11:19Z
author_association: MEMBER
diff_hunk: @@ -141,44 +152,56 @@ public static void ValidateProjectSpec(PackageSpec spec)
             }
         }
 
-        private static void ValidateFrameworks(PackageSpec spec, IEnumerable<string> files)
+        private static void ValidateFrameworks(PackageSpec spec, IEnumerable<string> files, ILogger logger)
         {
-            var frameworks = spec.TargetFrameworks.Select(f => f.FrameworkName).ToArray();
+            var frameworkNames = spec.TargetFrameworks.Select(f => f.FrameworkName).ToArray();
+            HashSet<string> invalidFrameworks = null;
 
             // Verify frameworks are valid
-            foreach (var framework in frameworks.Where(f => !f.IsSpecificFramework))
+            foreach (var framework in spec.TargetFrameworks.Where(f => !f.FrameworkName.IsSpecificFramework))
             {
-                var message = string.Format(
-                    CultureInfo.CurrentCulture,
-                    Strings.SpecValidationInvalidFramework,
-                    framework.GetShortFolderName());
+                if (invalidFrameworks == null)
path: src/NuGet.Core/NuGet.Commands/RestoreCommand/Utility/SpecValidationUtility.cs
user: login: Nigusu-Allehu
body: Great catch
created_at: 2025-03-05T17:35:12Z
author_association: MEMBER
diff_hunk: @@ -141,44 +152,48 @@ public static void ValidateProjectSpec(PackageSpec spec)
             }
         }
 
-        private static void ValidateFrameworks(PackageSpec spec, IEnumerable<string> files)
+        private static void ValidateFrameworks(PackageSpec spec, IEnumerable<string> files, ILogger logger)
         {
-            var frameworks = spec.TargetFrameworks.Select(f => f.FrameworkName).ToArray();
+            var invalidFrameworks = spec?.TargetFrameworks?
path: src/NuGet.Core/NuGet.Commands/RestoreCommand/Utility/SpecValidationUtility.cs
user: login: nkolev92
body: The likely scenario is that we do not have issues. 

We should deconstruct this with a for loop. 

We're allocating an array for the invalid frameworks despite not specifically needing it. 
There's also allocations for the where/select. 

This is called for every project and every restore and it's relatively easy to clean-up. 
Feel free to ping me if you want to chat.
created_at: 2025-03-05T17:55:57Z
author_association: MEMBER
diff_hunk: @@ -141,44 +152,55 @@ public static void ValidateProjectSpec(PackageSpec spec)
             }
         }
 
-        private static void ValidateFrameworks(PackageSpec spec, IEnumerable<string> files)
+        private static void ValidateFrameworks(PackageSpec spec, IEnumerable<string> files, ILogger logger)
         {
-            var frameworks = spec.TargetFrameworks.Select(f => f.FrameworkName).ToArray();
+            bool hasInvalidFrameworks = false;
 
-            // Verify frameworks are valid
-            foreach (var framework in frameworks.Where(f => !f.IsSpecificFramework))
+            foreach (var framework in (spec?.TargetFrameworks ?? new List<TargetFrameworkInformation>()))
path: src/NuGet.Core/NuGet.Commands/RestoreCommand/Utility/SpecValidationUtility.cs
user: login: nkolev92
body: Do we need the new list?
created_at: 2025-03-05T19:50:10Z
author_association: MEMBER
diff_hunk: @@ -141,44 +152,55 @@ public static void ValidateProjectSpec(PackageSpec spec)
             }
         }
 
-        private static void ValidateFrameworks(PackageSpec spec, IEnumerable<string> files)
+        private static void ValidateFrameworks(PackageSpec spec, IEnumerable<string> files, ILogger logger)
         {
-            var frameworks = spec.TargetFrameworks.Select(f => f.FrameworkName).ToArray();
+            bool hasInvalidFrameworks = false;
 
-            // Verify frameworks are valid
-            foreach (var framework in frameworks.Where(f => !f.IsSpecificFramework))
+            foreach (var framework in (spec?.TargetFrameworks ?? new List<TargetFrameworkInformation>()))
path: src/NuGet.Core/NuGet.Commands/RestoreCommand/Utility/SpecValidationUtility.cs
user: login: Nigusu-Allehu
body: That is incase TargetFrameworks is null or spec is null
created_at: 2025-03-05T19:58:59Z
author_association: MEMBER
diff_hunk: @@ -141,44 +152,55 @@ public static void ValidateProjectSpec(PackageSpec spec)
             }
         }
 
-        private static void ValidateFrameworks(PackageSpec spec, IEnumerable<string> files)
+        private static void ValidateFrameworks(PackageSpec spec, IEnumerable<string> files, ILogger logger)
         {
-            var frameworks = spec.TargetFrameworks.Select(f => f.FrameworkName).ToArray();
+            bool hasInvalidFrameworks = false;
 
-            // Verify frameworks are valid
-            foreach (var framework in frameworks.Where(f => !f.IsSpecificFramework))
+            foreach (var framework in (spec?.TargetFrameworks ?? new List<TargetFrameworkInformation>()))
path: src/NuGet.Core/NuGet.Commands/RestoreCommand/Utility/SpecValidationUtility.cs
user: login: nkolev92
body: By this point, spec can't be null, it would've thrown earlier. 

If TargetFrameworks was null, line 176 would fail, so we'd need to guard that as well. 

I'd just do a null check before calling the foreach.
created_at: 2025-03-05T20:13:36Z
author_association: MEMBER
diff_hunk: @@ -26,6 +27,11 @@ public static void ValidateDependencySpec(DependencyGraphSpec spec)
         /// Validate a dg file. This will throw a RestoreSpecException if there are errors.
         /// </summary>
         public static void ValidateDependencySpec(DependencyGraphSpec spec, HashSet<string> projectsToSkip)
+        {
+            ValidateDependencySpec(spec, projectsToSkip, NullLogger.Instance);
+        }
+
+        public static void ValidateDependencySpec(DependencyGraphSpec spec, HashSet<string> projectsToSkip, ILogger logger)
path: src/NuGet.Core/NuGet.Commands/RestoreCommand/Utility/SpecValidationUtility.cs
user: login: donnie-msft
body: `logger` can still be null here, and since this is a public method, could it be safer to null check and assign `logger = NullLogger.Instance` if it's null?

created_at: 2025-03-05T23:56:27Z
author_association: CONTRIBUTOR
diff_hunk: @@ -72,8 +81,19 @@ public void SpecValidationUtility_VerifyFrameworks_Unsupported()
 
             spec.AddProject(project);
 
-            // Act && Assert
-            AssertError(spec, "Invalid target framework");
+            // Act
+            try
+            {
+                SpecValidationUtility.ValidateDependencySpec(spec, new HashSet<string>(), mockLogger.Object);
path: test/NuGet.Core.Tests/NuGet.Commands.Test/SpecValidationUtilityTests.cs
user: login: donnie-msft
body: Try the `Should().Throw(...` FluentAssertion syntax for asserting thrown exceptions.
created_at: 2025-03-06T00:01:04Z
author_association: CONTRIBUTOR
diff_hunk: @@ -154,6 +154,8 @@ private static void ValidateProjectSpec(PackageSpec spec, ILogger logger)
 
         private static void ValidateFrameworks(PackageSpec spec, IEnumerable<string> files, ILogger logger)
         {
+            logger ??= NullLogger.Instance;
path: src/NuGet.Core/NuGet.Commands/RestoreCommand/Utility/SpecValidationUtility.cs
user: login: donnie-msft
body: Not here - If you do it here, all the other private methods will need the same check.
Instead move this to the public method which covers all the bases: https://github.com/NuGet/NuGet.Client/blob/84b23618275c993f4e188281294d8ac4f7dd70bf/src/NuGet.Core/NuGet.Commands/RestoreCommand/Utility/SpecValidationUtility.cs#L34
created_at: 2025-03-06T18:35:10Z
author_association: CONTRIBUTOR
diff_hunk: @@ -154,6 +154,8 @@ private static void ValidateProjectSpec(PackageSpec spec, ILogger logger)
 
         private static void ValidateFrameworks(PackageSpec spec, IEnumerable<string> files, ILogger logger)
         {
+            logger ??= NullLogger.Instance;
path: src/NuGet.Core/NuGet.Commands/RestoreCommand/Utility/SpecValidationUtility.cs
user: login: Nigusu-Allehu
body: Thanks
created_at: 2025-03-06T18:49:15Z
author_association: MEMBER
diff_hunk: @@ -141,44 +154,61 @@ public static void ValidateProjectSpec(PackageSpec spec)
             }
         }
 
-        private static void ValidateFrameworks(PackageSpec spec, IEnumerable<string> files)
+        private static void ValidateFrameworks(PackageSpec spec, IEnumerable<string> files, ILogger logger)
         {
-            var frameworks = spec.TargetFrameworks.Select(f => f.FrameworkName).ToArray();
 
-            // Verify frameworks are valid
-            foreach (var framework in frameworks.Where(f => !f.IsSpecificFramework))
+            if (spec.TargetFrameworks == null)
             {
-                var message = string.Format(
-                    CultureInfo.CurrentCulture,
-                    Strings.SpecValidationInvalidFramework,
-                    framework.GetShortFolderName());
+                throw RestoreSpecException.Create(Strings.SpecValidationNoFrameworks, files);
+            }
 
-                throw RestoreSpecException.Create(message, files);
+            bool hasInvalidFrameworks = false;
+
+            foreach (var framework in spec.TargetFrameworks)
+            {
+                if (!framework.FrameworkName.IsSpecificFramework)
+                {
+                    hasInvalidFrameworks |= true;
+                    var message = string.Format(CultureInfo.CurrentCulture, Strings.SpecValidationInvalidFramework, framework.TargetAlias);
+                    logger.Log(new RestoreLogMessage(LogLevel.Error, NuGetLogCode.NU1105, message));
+                }
+            }
+
+            if (hasInvalidFrameworks)
+            {
+                throw RestoreSpecException.Create("", files);
+            }
+
+            List<NuGetFramework> frameworkNames = new List<NuGetFramework>(spec.TargetFrameworks.Count);
+
+            foreach (var f in spec.TargetFrameworks)
path: src/NuGet.Core/NuGet.Commands/RestoreCommand/Utility/SpecValidationUtility.cs
user: login: jeffkl
body: I would combine the `foreach` and only loop through the list once.  Its okay to create a list with more items than you end up using.  This that case it might make a list with 3 items but only add 2, then after the `foreach` its just going to throw.  But .NET would know that a list with a capacity of 3 really only has 2 items in it.
created_at: 2025-03-06T18:57:23Z
author_association: CONTRIBUTOR
diff_hunk: @@ -141,44 +152,51 @@ public static void ValidateProjectSpec(PackageSpec spec)
             }
         }
 
-        private static void ValidateFrameworks(PackageSpec spec, IEnumerable<string> files)
+        private static void ValidateFrameworks(PackageSpec spec, IEnumerable<string> files, ILogger logger)
         {
-            var frameworks = spec.TargetFrameworks.Select(f => f.FrameworkName).ToArray();
+            var frameworkNames = spec.TargetFrameworks.Select(f => f.FrameworkName).ToArray();
+            HashSet<string> invalidFrameworks = new HashSet<string>();
 
             // Verify frameworks are valid
-            foreach (var framework in frameworks.Where(f => !f.IsSpecificFramework))
+            foreach (var framework in spec.TargetFrameworks.Where(f => !f.FrameworkName.IsSpecificFramework))
             {
-                var message = string.Format(
-                    CultureInfo.CurrentCulture,
-                    Strings.SpecValidationInvalidFramework,
-                    framework.GetShortFolderName());
+                if (!invalidFrameworks.Add(framework.TargetAlias))
+                {
+                    continue;
+                }
 
-                throw RestoreSpecException.Create(message, files);
+                var message = string.Format(CultureInfo.CurrentCulture, Strings.SpecValidationInvalidFramework, framework.TargetAlias);
+                logger.Log(new RestoreLogMessage(LogLevel.Error, NuGetLogCode.NU1105, message));
+            }
+
+            if (invalidFrameworks.Count > 0)
+            {
+                throw RestoreSpecException.Create("", files);
path: src/NuGet.Core/NuGet.Commands/RestoreCommand/Utility/SpecValidationUtility.cs
user: login: donnie-msft
body: If we're throwing, there's still reason to put a message that makes sense into that exception, regardless of logging that may have happened. 

An empty string here can result in an odd message with 2 periods like "Invalid restore input. ."
See:
https://github.com/NuGet/NuGet.Client/blob/dd3572abf6312f9ce45cc2052affd6e82d234d12/src/NuGet.Core/NuGet.Commands/RestoreCommand/RequestFactory/RestoreSpecException.cs#L55
created_at: 2025-03-06T19:17:24Z
author_association: CONTRIBUTOR
diff_hunk: @@ -141,44 +154,61 @@ public static void ValidateProjectSpec(PackageSpec spec)
             }
         }
 
-        private static void ValidateFrameworks(PackageSpec spec, IEnumerable<string> files)
+        private static void ValidateFrameworks(PackageSpec spec, IEnumerable<string> files, ILogger logger)
         {
-            var frameworks = spec.TargetFrameworks.Select(f => f.FrameworkName).ToArray();
 
-            // Verify frameworks are valid
-            foreach (var framework in frameworks.Where(f => !f.IsSpecificFramework))
+            if (spec.TargetFrameworks == null)
             {
-                var message = string.Format(
-                    CultureInfo.CurrentCulture,
-                    Strings.SpecValidationInvalidFramework,
-                    framework.GetShortFolderName());
+                throw RestoreSpecException.Create(Strings.SpecValidationNoFrameworks, files);
+            }
 
-                throw RestoreSpecException.Create(message, files);
+            bool hasInvalidFrameworks = false;
+
+            foreach (var framework in spec.TargetFrameworks)
+            {
+                if (!framework.FrameworkName.IsSpecificFramework)
+                {
+                    hasInvalidFrameworks |= true;
+                    var message = string.Format(CultureInfo.CurrentCulture, Strings.SpecValidationInvalidFramework, framework.TargetAlias);
+                    logger.Log(new RestoreLogMessage(LogLevel.Error, NuGetLogCode.NU1105, message));
+                }
+            }
+
+            if (hasInvalidFrameworks)
+            {
+                throw RestoreSpecException.Create("", files);
+            }
+
+            List<NuGetFramework> frameworkNames = new List<NuGetFramework>(spec.TargetFrameworks.Count);
+
+            foreach (var f in spec.TargetFrameworks)
path: src/NuGet.Core/NuGet.Commands/RestoreCommand/Utility/SpecValidationUtility.cs
user: login: donnie-msft
body: Also, can you rename `f` here please.
created_at: 2025-03-06T19:17:44Z
author_association: CONTRIBUTOR
diff_hunk: @@ -141,44 +152,51 @@ public static void ValidateProjectSpec(PackageSpec spec)
             }
         }
 
-        private static void ValidateFrameworks(PackageSpec spec, IEnumerable<string> files)
+        private static void ValidateFrameworks(PackageSpec spec, IEnumerable<string> files, ILogger logger)
         {
-            var frameworks = spec.TargetFrameworks.Select(f => f.FrameworkName).ToArray();
+            var frameworkNames = spec.TargetFrameworks.Select(f => f.FrameworkName).ToArray();
+            HashSet<string> invalidFrameworks = new HashSet<string>();
 
             // Verify frameworks are valid
-            foreach (var framework in frameworks.Where(f => !f.IsSpecificFramework))
+            foreach (var framework in spec.TargetFrameworks.Where(f => !f.FrameworkName.IsSpecificFramework))
             {
-                var message = string.Format(
-                    CultureInfo.CurrentCulture,
-                    Strings.SpecValidationInvalidFramework,
-                    framework.GetShortFolderName());
+                if (!invalidFrameworks.Add(framework.TargetAlias))
+                {
+                    continue;
+                }
 
-                throw RestoreSpecException.Create(message, files);
+                var message = string.Format(CultureInfo.CurrentCulture, Strings.SpecValidationInvalidFramework, framework.TargetAlias);
+                logger.Log(new RestoreLogMessage(LogLevel.Error, NuGetLogCode.NU1105, message));
+            }
+
+            if (invalidFrameworks.Count > 0)
+            {
+                throw RestoreSpecException.Create("", files);
path: src/NuGet.Core/NuGet.Commands/RestoreCommand/Utility/SpecValidationUtility.cs
user: login: Nigusu-Allehu
body: I don't think that would happen https://github.com/NuGet/NuGet.Client/blob/8101dac094a539403b5600f60e6f044fb88c60cf/src/NuGet.Core/NuGet.Commands/Strings.resx#L351 . The string does not have a period at the end.


If we were to put a message in the exception, it would look something like

```
NU1105: Invalid framework: ".9"
NU1105: Invalid framework: "unsupp1orted"
NU1105: Invalid framework: "1.222"
error : Invalid restore input. One or more invalid frameworks were detected. Input files: N:\trash\p7\p7\p7.csproj.
```

If we leave it as an empty message, it will look like
```
NU1105: Invalid framework: ".9"
NU1105: Invalid framework: "unsupp1orted"
NU1105: Invalid framework: "1.222"
error : Invalid restore input.  Input files: N:\trash\p7\p7\p7.csproj.
```

Personally, I think adding the message again is redundent. 
created_at: 2025-03-06T21:23:24Z
author_association: MEMBER
diff_hunk: @@ -141,44 +154,61 @@ public static void ValidateProjectSpec(PackageSpec spec)
             }
         }
 
-        private static void ValidateFrameworks(PackageSpec spec, IEnumerable<string> files)
+        private static void ValidateFrameworks(PackageSpec spec, IEnumerable<string> files, ILogger logger)
         {
-            var frameworks = spec.TargetFrameworks.Select(f => f.FrameworkName).ToArray();
 
-            // Verify frameworks are valid
-            foreach (var framework in frameworks.Where(f => !f.IsSpecificFramework))
+            if (spec.TargetFrameworks == null)
             {
-                var message = string.Format(
-                    CultureInfo.CurrentCulture,
-                    Strings.SpecValidationInvalidFramework,
-                    framework.GetShortFolderName());
+                throw RestoreSpecException.Create(Strings.SpecValidationNoFrameworks, files);
+            }
 
-                throw RestoreSpecException.Create(message, files);
+            bool hasInvalidFrameworks = false;
+
+            foreach (var framework in spec.TargetFrameworks)
+            {
+                if (!framework.FrameworkName.IsSpecificFramework)
+                {
+                    hasInvalidFrameworks |= true;
+                    var message = string.Format(CultureInfo.CurrentCulture, Strings.SpecValidationInvalidFramework, framework.TargetAlias);
+                    logger.Log(new RestoreLogMessage(LogLevel.Error, NuGetLogCode.NU1105, message));
+                }
+            }
+
+            if (hasInvalidFrameworks)
+            {
+                throw RestoreSpecException.Create("", files);
+            }
+
+            List<NuGetFramework> frameworkNames = new List<NuGetFramework>(spec.TargetFrameworks.Count);
+
+            foreach (var f in spec.TargetFrameworks)
path: src/NuGet.Core/NuGet.Commands/RestoreCommand/Utility/SpecValidationUtility.cs
user: login: Nigusu-Allehu
body: > I would combine the `foreach` and only loop through the list once. Its okay to create a list with more items than you end up using. This that case it might make a list with 3 items but only add 2, then after the `foreach` its just going to throw. But .NET would know that a list with a capacity of 3 really only has 2 items in it.

The second loop is there in hopes of avoiding allocation since we might throw an exception if an invalid framework is found. That way, we don’t allocate a list unless we know we need it. However, given that the list is small, the extra allocation is negligible, and I don’t mind switching to one loop either.

created_at: 2025-03-06T21:53:43Z
author_association: MEMBER
diff_hunk: @@ -141,44 +152,51 @@ public static void ValidateProjectSpec(PackageSpec spec)
             }
         }
 
-        private static void ValidateFrameworks(PackageSpec spec, IEnumerable<string> files)
+        private static void ValidateFrameworks(PackageSpec spec, IEnumerable<string> files, ILogger logger)
         {
-            var frameworks = spec.TargetFrameworks.Select(f => f.FrameworkName).ToArray();
+            var frameworkNames = spec.TargetFrameworks.Select(f => f.FrameworkName).ToArray();
+            HashSet<string> invalidFrameworks = new HashSet<string>();
 
             // Verify frameworks are valid
-            foreach (var framework in frameworks.Where(f => !f.IsSpecificFramework))
+            foreach (var framework in spec.TargetFrameworks.Where(f => !f.FrameworkName.IsSpecificFramework))
             {
-                var message = string.Format(
-                    CultureInfo.CurrentCulture,
-                    Strings.SpecValidationInvalidFramework,
-                    framework.GetShortFolderName());
+                if (!invalidFrameworks.Add(framework.TargetAlias))
+                {
+                    continue;
+                }
 
-                throw RestoreSpecException.Create(message, files);
+                var message = string.Format(CultureInfo.CurrentCulture, Strings.SpecValidationInvalidFramework, framework.TargetAlias);
+                logger.Log(new RestoreLogMessage(LogLevel.Error, NuGetLogCode.NU1105, message));
+            }
+
+            if (invalidFrameworks.Count > 0)
+            {
+                throw RestoreSpecException.Create("", files);
path: src/NuGet.Core/NuGet.Commands/RestoreCommand/Utility/SpecValidationUtility.cs
user: login: donnie-msft
body: You're referencing 2 strings here. I'm only talking about `InvalidRestoreInput` https://github.com/NuGet/NuGet.Client/blob/13aaaf31db2958ca85b0b98b8dea8fcf6a7961de/src/NuGet.Core/NuGet.Commands/Strings.Designer.cs#L1045

When this `message` is an empty string, I expect "Invalid restore input.__" where __ represents 2 unnecessary spaces.
This does not have "Input files:" in the string. 

That would be `InvalidRestoreInputWithFiles`. Again, I expect the empty string message to create this message: "Invalid restore input.___Input files: {1}." where ___ represents 3 unnecessary spaces and {1} is filled in with filenames.

I'm not following why it's good to omit the message in the exception. I don't think typically we say that logging a message means an exception should be generalized or with empty messages. It may be redundant but is there an issue with such redundancy?
created_at: 2025-03-08T01:08:52Z
author_association: CONTRIBUTOR
diff_hunk: @@ -141,44 +152,51 @@ public static void ValidateProjectSpec(PackageSpec spec)
             }
         }
 
-        private static void ValidateFrameworks(PackageSpec spec, IEnumerable<string> files)
+        private static void ValidateFrameworks(PackageSpec spec, IEnumerable<string> files, ILogger logger)
         {
-            var frameworks = spec.TargetFrameworks.Select(f => f.FrameworkName).ToArray();
+            var frameworkNames = spec.TargetFrameworks.Select(f => f.FrameworkName).ToArray();
+            HashSet<string> invalidFrameworks = new HashSet<string>();
 
             // Verify frameworks are valid
-            foreach (var framework in frameworks.Where(f => !f.IsSpecificFramework))
+            foreach (var framework in spec.TargetFrameworks.Where(f => !f.FrameworkName.IsSpecificFramework))
             {
-                var message = string.Format(
-                    CultureInfo.CurrentCulture,
-                    Strings.SpecValidationInvalidFramework,
-                    framework.GetShortFolderName());
+                if (!invalidFrameworks.Add(framework.TargetAlias))
+                {
+                    continue;
+                }
 
-                throw RestoreSpecException.Create(message, files);
+                var message = string.Format(CultureInfo.CurrentCulture, Strings.SpecValidationInvalidFramework, framework.TargetAlias);
+                logger.Log(new RestoreLogMessage(LogLevel.Error, NuGetLogCode.NU1105, message));
+            }
+
+            if (invalidFrameworks.Count > 0)
+            {
+                throw RestoreSpecException.Create("", files);
path: src/NuGet.Core/NuGet.Commands/RestoreCommand/Utility/SpecValidationUtility.cs
user: login: Nigusu-Allehu
body: I see your point about the space. It would indeed look a bit off. I don't think it would introduce 2 or 3 unnecessary spaces, just one in both cases. However, I agree that even a single extra space wouldn't look nice. To avoid this, I'll introduce a string to ensure proper formatting. Thanks for the suggestion!  
created_at: 2025-03-10T16:21:25Z
author_association: MEMBER
diff_hunk: @@ -141,44 +154,56 @@ public static void ValidateProjectSpec(PackageSpec spec)
             }
         }
 
-        private static void ValidateFrameworks(PackageSpec spec, IEnumerable<string> files)
+        private static void ValidateFrameworks(PackageSpec spec, IEnumerable<string> files, ILogger logger)
         {
-            var frameworks = spec.TargetFrameworks.Select(f => f.FrameworkName).ToArray();
+            if (spec.TargetFrameworks == null)
+            {
+                throw RestoreSpecException.Create(Strings.SpecValidationNoFrameworks, files);
+            }
 
-            // Verify frameworks are valid
-            foreach (var framework in frameworks.Where(f => !f.IsSpecificFramework))
+            bool hasInvalidFrameworks = false;
+            List<NuGetFramework> frameworkNames = new List<NuGetFramework>(spec.TargetFrameworks.Count);
+
+            foreach (var framework in spec.TargetFrameworks)
             {
-                var message = string.Format(
-                    CultureInfo.CurrentCulture,
-                    Strings.SpecValidationInvalidFramework,
-                    framework.GetShortFolderName());
+                frameworkNames.Add(framework.FrameworkName);
 
-                throw RestoreSpecException.Create(message, files);
+                if (!framework.FrameworkName.IsSpecificFramework)
+                {
+                    hasInvalidFrameworks |= true;
+                    var message = string.Format(CultureInfo.CurrentCulture, Strings.SpecValidationInvalidFramework, framework.TargetAlias);
+                    logger.Log(new RestoreLogMessage(LogLevel.Error, NuGetLogCode.NU1105, message));
path: src/NuGet.Core/NuGet.Commands/RestoreCommand/Utility/SpecValidationUtility.cs
user: login: jeffkl
body: Team Review: See if we can include the project path in the message so it shows properly in VS
created_at: 2025-03-11T22:13:21Z
author_association: CONTRIBUTOR
diff_hunk: @@ -141,44 +154,56 @@ public static void ValidateProjectSpec(PackageSpec spec)
             }
         }
 
-        private static void ValidateFrameworks(PackageSpec spec, IEnumerable<string> files)
+        private static void ValidateFrameworks(PackageSpec spec, IEnumerable<string> files, ILogger logger)
         {
-            var frameworks = spec.TargetFrameworks.Select(f => f.FrameworkName).ToArray();
+            if (spec.TargetFrameworks == null)
+            {
+                throw RestoreSpecException.Create(Strings.SpecValidationNoFrameworks, files);
+            }
 
-            // Verify frameworks are valid
-            foreach (var framework in frameworks.Where(f => !f.IsSpecificFramework))
+            bool hasInvalidFrameworks = false;
+            List<NuGetFramework> frameworkNames = new List<NuGetFramework>(spec.TargetFrameworks.Count);
+
+            foreach (var framework in spec.TargetFrameworks)
             {
-                var message = string.Format(
-                    CultureInfo.CurrentCulture,
-                    Strings.SpecValidationInvalidFramework,
-                    framework.GetShortFolderName());
+                frameworkNames.Add(framework.FrameworkName);
 
-                throw RestoreSpecException.Create(message, files);
+                if (!framework.FrameworkName.IsSpecificFramework)
+                {
+                    hasInvalidFrameworks |= true;
+                    var message = string.Format(CultureInfo.CurrentCulture, Strings.SpecValidationInvalidFramework, framework.TargetAlias);
+                    logger.Log(new RestoreLogMessage(LogLevel.Error, NuGetLogCode.NU1105, message));
path: src/NuGet.Core/NuGet.Commands/RestoreCommand/Utility/SpecValidationUtility.cs
user: login: Nigusu-Allehu
body: ![image](https://github.com/user-attachments/assets/05a1cf01-afd7-4881-892e-c095c790d5fa)
Updated
created_at: 2025-03-13T18:33:13Z
author_association: MEMBER
diff_hunk: @@ -171,7 +171,7 @@ private static void ValidateFrameworks(PackageSpec spec, IEnumerable<string> fil
                 if (!framework.FrameworkName.IsSpecificFramework)
                 {
                     hasInvalidFrameworks |= true;
-                    var message = string.Format(CultureInfo.CurrentCulture, Strings.SpecValidationInvalidFramework, framework.TargetAlias);
+                    var message = string.Format(CultureInfo.CurrentCulture, Strings.SpecValidationInvalidFramework, framework.TargetAlias, spec.FilePath);
path: src/NuGet.Core/NuGet.Commands/RestoreCommand/Utility/SpecValidationUtility.cs
user: login: jeffkl
body: Set the other properties of the `RestoreLogMessage` as well

https://github.com/NuGet/NuGet.Client/blob/dev/src/NuGet.Core/NuGet.Common/Errors/RestoreLogMessage.cs#L15

https://github.com/NuGet/NuGet.Client/blob/dev/src/NuGet.Core/NuGet.Common/Errors/RestoreLogMessage.cs#L17

Maybe look for other examples of how that class is instantiated?
created_at: 2025-03-13T19:03:14Z
author_association: CONTRIBUTOR
diff_hunk: @@ -141,44 +154,56 @@ public static void ValidateProjectSpec(PackageSpec spec)
             }
         }
 
-        private static void ValidateFrameworks(PackageSpec spec, IEnumerable<string> files)
+        private static void ValidateFrameworks(PackageSpec spec, IEnumerable<string> files, ILogger logger)
         {
-            var frameworks = spec.TargetFrameworks.Select(f => f.FrameworkName).ToArray();
+            if (spec.TargetFrameworks == null)
+            {
+                throw RestoreSpecException.Create(Strings.SpecValidationNoFrameworks, files);
+            }
 
-            // Verify frameworks are valid
-            foreach (var framework in frameworks.Where(f => !f.IsSpecificFramework))
+            bool hasInvalidFrameworks = false;
+            List<NuGetFramework> frameworkNames = new List<NuGetFramework>(spec.TargetFrameworks.Count);
+
+            foreach (var framework in spec.TargetFrameworks)
             {
-                var message = string.Format(
-                    CultureInfo.CurrentCulture,
-                    Strings.SpecValidationInvalidFramework,
-                    framework.GetShortFolderName());
+                frameworkNames.Add(framework.FrameworkName);
 
-                throw RestoreSpecException.Create(message, files);
+                if (!framework.FrameworkName.IsSpecificFramework)
+                {
+                    hasInvalidFrameworks |= true;
+                    var message = string.Format(CultureInfo.CurrentCulture, Strings.SpecValidationInvalidFramework, framework.TargetAlias);
+                    logger.Log(new RestoreLogMessage(LogLevel.Error, NuGetLogCode.NU1105, message));
path: src/NuGet.Core/NuGet.Commands/RestoreCommand/Utility/SpecValidationUtility.cs
user: login: nkolev92
body: Can we use the log property instead of calling out the project in the message?

I think it'll avoid duplication and allow all the filtering to happen.
created_at: 2025-03-13T19:06:22Z
author_association: MEMBER
diff_hunk: @@ -141,44 +154,56 @@ public static void ValidateProjectSpec(PackageSpec spec)
             }
         }
 
-        private static void ValidateFrameworks(PackageSpec spec, IEnumerable<string> files)
+        private static void ValidateFrameworks(PackageSpec spec, IEnumerable<string> files, ILogger logger)
         {
-            var frameworks = spec.TargetFrameworks.Select(f => f.FrameworkName).ToArray();
+            if (spec.TargetFrameworks == null)
+            {
+                throw RestoreSpecException.Create(Strings.SpecValidationNoFrameworks, files);
+            }
 
-            // Verify frameworks are valid
-            foreach (var framework in frameworks.Where(f => !f.IsSpecificFramework))
+            bool hasInvalidFrameworks = false;
+            List<NuGetFramework> frameworkNames = new List<NuGetFramework>(spec.TargetFrameworks.Count);
+
+            foreach (var framework in spec.TargetFrameworks)
             {
-                var message = string.Format(
-                    CultureInfo.CurrentCulture,
-                    Strings.SpecValidationInvalidFramework,
-                    framework.GetShortFolderName());
+                frameworkNames.Add(framework.FrameworkName);
 
-                throw RestoreSpecException.Create(message, files);
+                if (!framework.FrameworkName.IsSpecificFramework)
+                {
+                    hasInvalidFrameworks |= true;
+                    var message = string.Format(CultureInfo.CurrentCulture, Strings.SpecValidationInvalidFramework, framework.TargetAlias);
+                    logger.Log(new RestoreLogMessage(LogLevel.Error, NuGetLogCode.NU1105, message));
path: src/NuGet.Core/NuGet.Commands/RestoreCommand/Utility/SpecValidationUtility.cs
user: login: Nigusu-Allehu
body: Updated. I set the RestoreLogMessage.FilePath. However there is a RestoreLogMessage.ProjectPath as well as mentioned in https://github.com/NuGet/NuGet.Client/pull/6291#discussion_r1994149257. I am assuming FilePath is in general for non project/project files and ProjectPath for project files. If I set FilePath, do we need to set ProjectPath? It works well in VS with FilePath set. Allows opening if the project file with the issue.
created_at: 2025-03-17T23:33:30Z
author_association: MEMBER
diff_hunk: @@ -141,44 +154,56 @@ public static void ValidateProjectSpec(PackageSpec spec)
             }
         }
 
-        private static void ValidateFrameworks(PackageSpec spec, IEnumerable<string> files)
+        private static void ValidateFrameworks(PackageSpec spec, IEnumerable<string> files, ILogger logger)
         {
-            var frameworks = spec.TargetFrameworks.Select(f => f.FrameworkName).ToArray();
+            if (spec.TargetFrameworks == null)
+            {
+                throw RestoreSpecException.Create(Strings.SpecValidationNoFrameworks, files);
+            }
 
-            // Verify frameworks are valid
-            foreach (var framework in frameworks.Where(f => !f.IsSpecificFramework))
+            bool hasInvalidFrameworks = false;
+            List<NuGetFramework> frameworkNames = new List<NuGetFramework>(spec.TargetFrameworks.Count);
+
+            foreach (var framework in spec.TargetFrameworks)
             {
-                var message = string.Format(
-                    CultureInfo.CurrentCulture,
-                    Strings.SpecValidationInvalidFramework,
-                    framework.GetShortFolderName());
+                frameworkNames.Add(framework.FrameworkName);
 
-                throw RestoreSpecException.Create(message, files);
+                if (!framework.FrameworkName.IsSpecificFramework)
+                {
+                    hasInvalidFrameworks |= true;
+                    var message = string.Format(CultureInfo.CurrentCulture, Strings.SpecValidationInvalidFramework, framework.TargetAlias);
+                    logger.Log(new RestoreLogMessage(LogLevel.Error, NuGetLogCode.NU1105, message) { FilePath = spec.FilePath });
path: src/NuGet.Core/NuGet.Commands/RestoreCommand/Utility/SpecValidationUtility.cs
user: login: nkolev92
body: Let's set both: 

https://github.com/NuGet/NuGet.Client/blob/c0138ebc315a948f89e4340aa09a72d8c05d744b/src/NuGet.Core/NuGet.Commands/RestoreCommand/Logging/RestoreCollectorLogger.cs#L171-L179 sets both.
created_at: 2025-03-18T20:20:28Z
author_association: MEMBER

PR comments:
user: login: nkolev92
created_at: 2025-03-06T22:06:07Z
body: Out of curiosity, have you tested how Visual Studio behaves here?
I just wanted to make sure we're not double validating and posting these errors twice in VS.
author_association: MEMBER
user: login: Nigusu-Allehu
created_at: 2025-03-10T16:23:31Z
body: > Out of curiosity, have you tested how Visual Studio behaves here? I just wanted to make sure we're not double validating and posting these errors twice in VS.

There is some redundancy introduced here. Mainly because there already exists NETSDK1013
![image](https://github.com/user-attachments/assets/afcd098e-de16-4dc6-9b48-74ce2076d28c)

author_association: MEMBER
user: login: jeffkl
created_at: 2025-03-18T22:16:36Z
body: @Nigusu-Allehu what does the error look like now at the console?
author_association: CONTRIBUTOR
user: login: Nigusu-Allehu
created_at: 2025-03-20T23:18:26Z
body: > @Nigusu-Allehu what does the error look like now at the console?

![image](https://github.com/user-attachments/assets/b7866dd2-e918-403c-a37c-45675105f310)
Added in the description as well
author_association: MEMBER
user: login: Nigusu-Allehu
created_at: 2025-03-21T21:20:45Z
body: Just noticed auto merge was enabled. If there is anyone that didn't get to comment because of this, please let me know.
author_association: MEMBER