Sr. Content Developer at Microsoft, working remotely in PA, TechBash conference organizer, former Microsoft MVP, Husband, Dad and Geek.
163331 stories
·
33 followers

Global Visual Studio Files

1 Share

Introduction

You may not know, but Visual Studio (or more accurately, MSBuild and other tools used implicitly) can make use of local files to configure some aspects of its working. In this post I will introduce and explain why we should use (at least) some of them. These files apply regardless of the solution format in use, are not related to a specific format.

Here is the list with a brief summary of what each file is for. Of course, other files may be used for other purposes and different technologies somewhat outside the control of Visual Studio/MSBuild (e.g., NPM, Less, etc).

File Purpose
Directory.Build.props Defines shared MSBuild properties for projects under that directory tree. Imported early in the build. Commonly used for target frameworks, nullable settings, analysers, company metadata, warning levels, etc.
Directory.Build.targets Defines shared MSBuild targets/items/tasks for projects under that directory tree. Imported late in the build. Commonly used for custom build steps, validation, packaging hooks, generated files, etc.
msbuild.rsp Default command-line switches to MSBuild.exe for a specific project.Visual Studio does not apply any .rsp response file. Better to use Directory.Build.rsp as it applies to all projects. Not commonly used.
Directory.Build.rsp MSBuild response file for passing command-line switches automatically to builds under a directory. Visual Studio does not apply any .rsp response file. Not commonly used.
Directory.Packages.props Used for Central Package Management. Defines NuGet package versions in one place with <PackageVersion />, so individual projects can reference packages without specifying versions.
Directory.Solution.props Less common. Solution-level MSBuild props imported when building a .slnx solution in newer MSBuild scenarios. Used for solution-wide properties.
Directory.Solution.targets Less common. Solution-level targets imported when building a solution. Used for solution-wide custom build logic.
global.json Pins the .NET SDK version used by the solution/repo. Helps ensure developers and CI use the same SDK.
NuGet.config Defines NuGet package sources, credentials behaviour, package restore settings, and source mapping. Can apply repo-wide depending on location.
dotnet-tools.json File created when you install a tool for your project with dotnet tool install command. This file contains details like version, installation command, etc, about all the tools installed for your project.
libman.json Manifest file for the Library Manager (LibMan) tool used in ASP.NET Core and web projects. It specifies which client-side libraries (like jQuery, Bootstrap, or Lodash, amongst others) to fetch, from which sources, and where to save them in your project.
.editorconfig Defines code style, formatting, analyser severity, suppression, naming rules, etc. Applies to files by directory scope and is honored by Visual Studio, Roslyn analysers, and many editors.
.globalconfig Repo-wide analyser configuration file for Roslyn. Similar to .editorconfig, but intended for global analyser settings rather than per-directory file style.
packages.lock.json NuGet lock file. Records exact resolved package dependency graph for repeatable restores. Usually generated per project, but can be managed repo-wide depending on restore setup.
launchSettings.json A configuration file within ASP.NET Core applications (Properties folder) to define various profiles for how the application can be launched during development, in the local machine.

Directory.Build.props

This file contains properties that should apply to all projects in the solution, which is generally good for coherence. This example applies .NET 10 with the latest C# language specs, whether to use C# nullable reference types and implicit usings, and if the output should be deterministic.

<Project>
  <PropertyGroup>
    <TargetFramework>net10.0</TargetFramework>
    <Nullable>enable</Nullable>
    <ImplicitUsings>enable</ImplicitUsings>
    <LangVersion>latest</LangVersion>
    <Deterministic>true</Deterministic>
  </PropertyGroup>
</Project>

Directory.Build.targets

This one defines targets (which can have conditions) for the projects in the directory. This example ensures that project MyProject.DomainModel does not reference any other project or NuGet package.

<Project>
  <Target Name="ValidateDomainDependencies"
          BeforeTargets="ResolveReferences"
          Condition="'$(MSBuildProjectName)' == 'MyProject.DomainModel'">
    <Error Condition="'@(PackageReference)' != ''"
           Text="MyProject.DomainModel must not reference NuGet packages." />
<Error Condition="'@(ProjectReference)' != ''" Text="MyProject.DomainModel must not reference other projects." />
</Target> </Project>

msbuild.rsp and Directory.Build.rsp

These files contain parameter options to be passed to MSBuild. The difference between the two is that the first should be placed in a project directory, and the second, in the root of a repository, so that it applies to all projects underneath it. Both are only applied by MSBuild, the Visual Studio IDE ignores them. Use %MSBuildThisFileDirectory% to reference the directory where the file is located. Here's a sample that enables some settings.

# Enable parallel builds across all available CPU cores
-maxCpuCount

# Restore packages and build concurrently
-restore
-m

# Enforce clean terminal output (displays warnings/errors at the end)
-clp:Summary;ErrorsOnly;WarningsOnly

# Automatically configure binlog generation using the file's current directory
-bl:%MSBuildThisFileDirectory%build.binlog

# Define continuous integration parameters if executing a hosted build
-p:ContinuousIntegrationBuild=true

Directory.Packages.props

This file defines NuGet package versions for the entire solution (all projects in a sub directory). This ensures that the same versions are used everywhere, then it's just a matter of individual projects referencing a NuGet package, without specifying its version. This is called Central Package Management and here is a sample that defines some NuGet package versions:

<Project>
  <PropertyGroup>
    <ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
    <CentralPackageTransitivePinningEnabled>true</CentralPackageTransitivePinningEnabled>
  </PropertyGroup>
  <ItemGroup>
    <PackageVersion Include="Microsoft.EntityFrameworkCore.Design" Version="10.0.10" />
    <PackageVersion Include="Microsoft.EntityFrameworkCore.Sqlite" Version="10.0.10" />
    <PackageVersion Include="Microsoft.EntityFrameworkCore.Tools" Version="10.0.10" />
    <PackageVersion Include="Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore" Version="10.0.10" />
    <PackageVersion Include="Microsoft.Extensions.TimeProvider.Testing" Version="10.8.0" />
    <PackageVersion Include="Microsoft.NET.Test.Sdk" Version="18.8.1" />
    <PackageVersion Include="xunit" Version="2.9.3" />
    <PackageVersion Include="xunit.runner.visualstudio" Version="3.1.5" />
  </ItemGroup>
</Project>

Directory.Solution.props

This is a specialised MSBuild file used to customize configuration settings specifically for solution builds. Not so commonly used, but here is an example:

<Project>
  <PropertyGroup>
    <!-- Speeds up solution restores via static graph evaluation -->
    <RestoreUseStaticGraphEvaluation>true</RestoreUseStaticGraphEvaluation>
<!-- Some solution-wide property -->
<SomeSolutionWideProperty>True</SomeSolutionWideProperty> </PropertyGroup> </Project>

Directory.Solution.targets

Directory.Solution.targets is an MSBuild file placed in the root path above a solution file to customize or extend the build process at the solution level. The following example displays a message after the build finishes:

<Project>
  <Target Name="CustomSolutionPostBuild" AfterTargets="Build">
    <Message Importance="high" Text="Finished building solution in $(SolutionDir)" />
  </Target>
</Project>

global.json

This file allows specifying a fixed .NET version for an entire directory. You should use a global.json file in .NET development when you need to lock, pin, or strictly manage the .NET SDK version used to execute .NET CLI commands. By default, the .NET CLI always uses the latest version of the SDK installed on your machine; you need a global.json when that default behavior causes issues. Example:

{
  "sdk": {
    "version": "10.0.302",
    "rollForward": "latestPatch",
    "allowPrerelease": false
  }
}

NuGet.config

In this file we specify NuGet package sources, which can possibly include credentials, for private repos. This is used when restoring NuGet packages. Here's a small example:

<?xml version="1.0" encoding="utf-8"?>
<configuration>
  <packageSources>
    <add key="nuget" value="https://api.nuget.org/v3/index.json" />
<add key="myprivaterepo" value="https://my.private.repo/nuget/index.json" /> </packageSources> </configuration>

dotnet-tools.json

The dotnet-tools.json file is a configuration manifest used by the .NET CLI to manage local tools specific to a project or repository, like, for example, EF. An example:

{
  "version": 1,
  "isRoot": true,
  "tools": {
    "dotnet-ef": {
      "version": "10.0.8",
      "commands": [
        "dotnet-ef"
      ],
      "rollForward": false
    }
  }
}

libman.json

This is the configuration manifest file used by LibMan (Library Manager) to download and manage client-side web libraries - like jQuery, Bootstrap, or Lodash - directly into web projects without needing heavy package managers like npm. Here's an example:

{
  "version": "1.0",
  "defaultProvider": "cdnjs",
  "libraries": [
    {
      "library": "jquery@3.7.1",
      "destination": "wwwroot/lib/jquery",
      "files": [
        "jquery.min.js",
        "jquery.min.map"
      ]
    },
    {
      "library": "bootstrap@5.3.3",
      "destination": "wwwroot/lib/bootstrap"
    }
  ]
}

.editorconfig

Configures IDE style preferences and analysis rules, for all files (*), or specific files (*.cs, for example). Here is a big example from https://github.com/dotnet/samples/blob/main/.editorconfig:

# editorconfig.org

# top-most EditorConfig file
root = true

# Default settings:
# A newline ending every file
# Use 4 spaces as indentation
[*]
insert_final_newline = true
indent_style = space
indent_size = 4

[project.json]
indent_size = 2

# C# and Visual Basic files
[*.{cs,vb}]
charset = utf-8-bom

# Code quality analyzers
dotnet_analyzer_diagnostic.category-Security.severity = error
dotnet_code_quality.ca1802.api_surface = private, internal

# Miscellaneous style rules
dotnet_sort_system_directives_first = true
dotnet_style_predefined_type_for_locals_parameters_members = true:suggestion
dotnet_style_predefined_type_for_member_access = true:suggestion

# avoid this. unless absolutely necessary
dotnet_style_qualification_for_field = false:suggestion
dotnet_style_qualification_for_property = false:suggestion
dotnet_style_qualification_for_method = false:suggestion
dotnet_style_qualification_for_event = false:suggestion

# name all constant fields using PascalCase
dotnet_naming_rule.constant_fields_should_be_pascal_case.severity = suggestion
dotnet_naming_rule.constant_fields_should_be_pascal_case.symbols  = constant_fields
dotnet_naming_rule.constant_fields_should_be_pascal_case.style    = pascal_case_style
dotnet_naming_symbols.constant_fields.applicable_kinds   = field
dotnet_naming_symbols.constant_fields.required_modifiers = const
dotnet_naming_style.pascal_case_style.capitalization = pascal_case

# static fields should have s_ prefix
dotnet_naming_rule.static_fields_should_have_prefix.severity = suggestion
dotnet_naming_rule.static_fields_should_have_prefix.symbols  = static_fields
dotnet_naming_rule.static_fields_should_have_prefix.style    = static_prefix_style
dotnet_naming_symbols.static_fields.applicable_kinds   = field
dotnet_naming_symbols.static_fields.required_modifiers = static
dotnet_naming_symbols.static_fields.applicable_accessibilities = private, internal, private_protected
dotnet_naming_style.static_prefix_style.required_prefix = s_
dotnet_naming_style.static_prefix_style.capitalization = camel_case 

# internal and private fields should be _camelCase
dotnet_naming_rule.camel_case_for_private_internal_fields.severity = suggestion
dotnet_naming_rule.camel_case_for_private_internal_fields.symbols  = private_internal_fields
dotnet_naming_rule.camel_case_for_private_internal_fields.style    = camel_case_underscore_style
dotnet_naming_symbols.private_internal_fields.applicable_kinds = field
dotnet_naming_symbols.private_internal_fields.applicable_accessibilities = private, internal
dotnet_naming_style.camel_case_underscore_style.required_prefix = _
dotnet_naming_style.camel_case_underscore_style.capitalization = camel_case 

# Code quality
dotnet_style_readonly_field = true:suggestion
dotnet_code_quality_unused_parameters = non_public:suggestion

# Expression-level preferences
dotnet_style_object_initializer = true:suggestion
dotnet_style_collection_initializer = true:suggestion
dotnet_style_explicit_tuple_names = true:suggestion
dotnet_style_coalesce_expression = true:suggestion
dotnet_style_null_propagation = true:suggestion
dotnet_style_prefer_is_null_check_over_reference_equality_method = true:suggestion
dotnet_style_prefer_inferred_tuple_names = true:suggestion
dotnet_style_prefer_inferred_anonymous_type_member_names = true:suggestion
dotnet_style_prefer_auto_properties = true:suggestion
dotnet_style_prefer_conditional_expression_over_assignment = true:refactoring
dotnet_style_prefer_conditional_expression_over_return = true:refactoring

# C# files
[*.cs]
# New line preferences
csharp_new_line_before_open_brace = all
csharp_new_line_before_else = true
csharp_new_line_before_catch = true
csharp_new_line_before_finally = true
csharp_new_line_before_members_in_object_initializers = true
csharp_new_line_before_members_in_anonymous_types = true
csharp_new_line_between_query_expression_clauses = true

# Indentation preferences
csharp_indent_block_contents = true
csharp_indent_braces = false
csharp_indent_case_contents = true
csharp_indent_case_contents_when_block = true
csharp_indent_switch_labels = true
csharp_indent_labels = one_less_than_current

# Modifier preferences
csharp_preferred_modifier_order = public,private,protected,internal,static,extern,new,virtual,abstract,sealed,override,readonly,unsafe,volatile,async:suggestion

# Code style defaults
csharp_using_directive_placement = outside_namespace:suggestion
csharp_prefer_braces = true:refactoring
csharp_preserve_single_line_blocks = true:none
csharp_preserve_single_line_statements = false:none
csharp_prefer_static_local_function = true:suggestion
csharp_prefer_simple_using_statement = false:none
csharp_style_prefer_switch_expression = true:suggestion

# Expression-bodied members
csharp_style_expression_bodied_methods = true:refactoring
csharp_style_expression_bodied_constructors = true:refactoring
csharp_style_expression_bodied_operators = true:refactoring
csharp_style_expression_bodied_properties = true:refactoring
csharp_style_expression_bodied_indexers = true:refactoring
csharp_style_expression_bodied_accessors = true:refactoring
csharp_style_expression_bodied_lambdas = true:refactoring
csharp_style_expression_bodied_local_functions = true:refactoring

# Pattern matching
csharp_style_pattern_matching_over_is_with_cast_check = true:suggestion
csharp_style_pattern_matching_over_as_with_null_check = true:suggestion
csharp_style_inlined_variable_declaration = true:suggestion

# Expression-level preferences
csharp_prefer_simple_default_expression = true:suggestion

# Null checking preferences
csharp_style_throw_expression = true:suggestion
csharp_style_conditional_delegate_call = true:suggestion

# Other features
csharp_style_prefer_index_operator = false:none
csharp_style_prefer_range_operator = false:none
csharp_style_pattern_local_over_anonymous_function = false:none

# Space preferences
csharp_space_after_cast = false
csharp_space_after_colon_in_inheritance_clause = true
csharp_space_after_comma = true
csharp_space_after_dot = false
csharp_space_after_keywords_in_control_flow_statements = true
csharp_space_after_semicolon_in_for_statement = true
csharp_space_around_binary_operators = before_and_after
csharp_space_around_declaration_statements = do_not_ignore
csharp_space_before_colon_in_inheritance_clause = true
csharp_space_before_comma = false
csharp_space_before_dot = false
csharp_space_before_open_square_brackets = false
csharp_space_before_semicolon_in_for_statement = false
csharp_space_between_empty_square_brackets = false
csharp_space_between_method_call_empty_parameter_list_parentheses = false
csharp_space_between_method_call_name_and_opening_parenthesis = false
csharp_space_between_method_call_parameter_list_parentheses = false
csharp_space_between_method_declaration_empty_parameter_list_parentheses = false
csharp_space_between_method_declaration_name_and_open_parenthesis = false
csharp_space_between_method_declaration_parameter_list_parentheses = false
csharp_space_between_parentheses = false
csharp_space_between_square_brackets = false

# Types: use keywords instead of BCL types, and permit var only when the type is clear
csharp_style_var_for_built_in_types = false:suggestion
csharp_style_var_when_type_is_apparent = false:none
csharp_style_var_elsewhere = false:suggestion

# Visual Basic files
[*.vb]
# Modifier preferences
visual_basic_preferred_modifier_order = Partial,Default,Private,Protected,Public,Friend,NotOverridable,Overridable,MustOverride,Overloads,Overrides,MustInherit,NotInheritable,Static,Shared,Shadows,ReadOnly,WriteOnly,Dim,Const,WithEvents,Widening,Narrowing,Custom,Async:suggestion

# C++ Files
[*.{cpp,h,in}]
curly_bracket_next_line = true
indent_brace_style = Allman

# Xml project files
[*.{csproj,vbproj,vcxproj,vcxproj.filters,proj,nativeproj,locproj}]
indent_size = 2

# Xml build files
[*.builds]
indent_size = 2

# Xml files
[*.{xml,stylecop,resx,ruleset}]
indent_size = 2

# Xml config files
[*.{props,targets,config,nuspec}]
indent_size = 2

# Shell scripts
[*.sh]
end_of_line = lf
[*.{cmd, bat}]
end_of_line = crlf

.globalconfig

The core difference between .editorconfig and .globalconfig is that .editorconfig is path-based and configures both IDE text editor styles and code diagnostics, whereas .globalconfig is compilation-based, ignores folder structures, and is designed purely for code analysis rules. It should have is_global  = true in order to apply to all files. If rules conflict between an .editorconfig file and a .globalconfig file, the settings in .editorconfig take precedence. See also this and this.

is_global = true

# Set the severity of a specific analyzer rule project-wide
dotnet_diagnostic.CA1822.severity = warning
dotnet_diagnostic.CA1056.severity = none

It is possible to specify one config file per project:

<Project>
  <ItemGroup>
    <GlobalAnalyzerConfigFiles Include="Relative/Path/To/PackageName.globalconfig" />
  </ItemGroup>
</Project>

packages.lock.json

NuGet uses the packages.lock.json file at the project root directory to restore packages for PackageReference based projects. Unlike npm, NuGet does not generate this file by default, it must first be enabled:

<PropertyGroup>
    <RestorePackagesWithLockFile>true</RestorePackagesWithLockFile>
</PropertyGroup>

This file serves the exact same high-level purpose as its Node.js counterpart, but it operates explicitly within the .NET ecosystem to achieve repeatable and deterministic package restores. When using PackageReference in your .csproj files, NuGet naturally resolves dependencies using a "lowest applicable version" rule. However, it can still be affected by floating versions or upstream changes to deep, indirect sub-dependencies. The lock file fixes this completely and it should be checked-in to source control.

For CI/CD builds, you can use the following command line options to enable locked mode:

dotnet restore --locked-mode

launchSettings.json

In .NET and ASP.NET Core, launchSettings.json is a configuration file used exclusively in local development machines to define how your application starts up. An example with HTTP and HTTPS and also Docker Compose launch scenarios:

{
  "$schema": "http://json.schemastore.org/launchsettings.json",
  "profiles": {
    "http": {
      "commandName": "Project",
      "dotnetRunMessages": true,
      "launchBrowser": true,
      "applicationUrl": "http://localhost:5066",
      "environmentVariables": {
        "ASPNETCORE_ENVIRONMENT": "Development"
      }
    },
    "https": {
      "commandName": "Project",
      "dotnetRunMessages": true,
      "launchBrowser": true,
      "applicationUrl": "https://localhost:7107;http://localhost:5066",
      "environmentVariables": {
        "ASPNETCORE_ENVIRONMENT": "Development"
      }
    },
"Docker Compose": { "commandName": "DockerCompose", "commandVersion": "1.0", "serviceActions": { "my-web-api": "StartDebugging", "sql-database": "StartWithoutDebugging", "redis-cache": "DoNotStart" } } } }

Conclusion

And this is it. As a rule of thumb, this is what I'd recommend:

  1. Use Directory.Build.props for shared project settings
  2. Use Directory.Build.targets for shared build behaviour
  3. Use Directory.Packages.props for shared NuGet package versions
  4. Use global.json for SDK version consistency
  5. Use a local NuGet.config file for custom repositories
  6. Use .editorconfig for code style and analyser rules

As always, feel free to comment or correct-me if there is need!

Read the whole story
alvinashcraft
just a second ago
reply
Pennsylvania, USA
Share this story
Delete

Reducing the size of your authentication cookie with ITicketStore

1 Share

We are currently testing a new WAF for our organization. After activating it, we noticed that some requests never hit our server. Further investigation showed that the root cause was linked to our authentication cookies becoming to big. As the WAF had very strict limits configured, the request was blocked.

The cause is how cookie authentication in ASP.NET Core works by default. The complete AuthenticationTicket (the claims, the properties and the tokens) is serialized, encrypted and put in the cookie. The bigger your identity, the bigger your cookie.

Of course, we could just change the WAF configuration and increase the limits but isn't there a better way that allows us to limit the cookie size?

Let's find out together...

Keep the ticket on the server

Cookie authentication has an extension point for this: ITicketStore. When you provide an implementation through CookieAuthenticationOptions.SessionStore, the ticket is stored server-side. The cookie only contains a session key.

The interface is small:

public interface ITicketStore
{
    Task<string> StoreAsync(AuthenticationTicket ticket);
    Task RenewAsync(string key, AuthenticationTicket ticket);
    Task<AuthenticationTicket?> RetrieveAsync(string key);
    Task RemoveAsync(string key);
}

Let me walk you through the different methods:

  • StoreAsync is called at sign-in and returns the key that ends up in the cookie.
  • RenewAsync is called when the ticket is refreshed (sliding expiration).
  • RetrieveAsync is called on every request to rebuild the ticket from the key.
  • RemoveAsync is called at sign-out.

A first implementation using IMemoryCache

We start with the simplest version:

using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.Extensions.Caching.Memory;

public class MemoryCacheTicketStore : ITicketStore
{
    private const string KeyPrefix = "AuthSessionStore-";
    private readonly IMemoryCache _cache;

    public MemoryCacheTicketStore(IMemoryCache cache)
    {
        _cache = cache;
    }

    public Task<string> StoreAsync(AuthenticationTicket ticket)
    {
        var key = KeyPrefix + Guid.NewGuid();
        SetEntry(key, ticket);
        return Task.FromResult(key);
    }

    public Task RenewAsync(string key, AuthenticationTicket ticket)
    {
        SetEntry(key, ticket);
        return Task.CompletedTask;
    }

    public Task<AuthenticationTicket?> RetrieveAsync(string key)
    {
        _cache.TryGetValue(key, out AuthenticationTicket? ticket);
        return Task.FromResult(ticket);
    }

    public Task RemoveAsync(string key)
    {
        _cache.Remove(key);
        return Task.CompletedTask;
    }

    private void SetEntry(string key, AuthenticationTicket ticket)
    {
        var options = new MemoryCacheEntryOptions();

        var expiresUtc = ticket.Properties.ExpiresUtc;
        if (expiresUtc.HasValue)
        {
            options.SetAbsoluteExpiration(expiresUtc.Value);
        }

        _cache.Set(key, ticket, options);
    }
}

Register it

The SessionStore property needs an instance, but our store depends on IMemoryCache. So let the options system resolve it from the container:

builder.Services.AddMemoryCache();
builder.Services.AddSingleton<ITicketStore, MemoryCacheTicketStore>();

builder.Services
    .AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
    .AddCookie();

builder.Services
    .AddOptions<CookieAuthenticationOptions>(CookieAuthenticationDefaults.AuthenticationScheme)
    .Configure<ITicketStore>((options, store) => options.SessionStore = store);

Sign in as you always did. If you look at the cookie in your browser dev tools now, it’s only a few hundred bytes, whatever the number of claims.

Remark: The in-memory version only works for a single instance. As soon as you scale out (multiple replicas in Azure Container Apps, for example), a request that lands on another instance can’t find the ticket and the user is signed out. And every restart signs out all users.

Moving to a distributed cache

For multiple instances, store the ticket in a shared cache. IDistributedCache works with Redis, SQL Server and others. The difference is that you have to serialize the ticket yourself. ASP.NET Core has a TicketSerializer for that:

public class DistributedCacheTicketStore : ITicketStore
{
    private const string KeyPrefix = "AuthSessionStore-";
    private readonly IDistributedCache _cache;

    public DistributedCacheTicketStore(IDistributedCache cache)
    {
        _cache = cache;
    }

    public async Task<string> StoreAsync(AuthenticationTicket ticket)
    {
        var key = KeyPrefix + Guid.NewGuid();
        await RenewAsync(key, ticket);
        return key;
    }

    public Task RenewAsync(string key, AuthenticationTicket ticket)
    {
        var options = new DistributedCacheEntryOptions();

        var expiresUtc = ticket.Properties.ExpiresUtc;
        if (expiresUtc.HasValue)
        {
            options.SetAbsoluteExpiration(expiresUtc.Value);
        }

        var bytes = TicketSerializer.Default.Serialize(ticket);
        return _cache.SetAsync(key, bytes, options);
    }

    public async Task<AuthenticationTicket?> RetrieveAsync(string key)
    {
        var bytes = await _cache.GetAsync(key);
        return bytes is null ? null : TicketSerializer.Default.Deserialize(bytes);
    }

    public Task RemoveAsync(string key) => _cache.RemoveAsync(key);
}

The registration stays the same, only the implementation changes.

Remark: The ticket now lives in your cache and contains the user’s claims (and tokens if you saved them). Treat that cache with the same care as a database: restrict access and encrypt the connection. You can even take it one step further and encrypt the ticket itself using the DataProtection api.

Be aware of the trade-off

Server-side storage brings something extra: you can invalidate a session. Remove the key from the store and the user is signed out on the next request. With a cookie-only ticket that’s not possible, because the cookie is valid until it expires.

The price is a lookup on every request, and a cache that is now part of your authentication path. If it’s down, nobody can sign in.

So the question becomes: do you need it? A small cookie with a handful of claims doesn’t need a ticket store. Large claim sets, saved tokens or the need to revoke sessions are good reasons to add one.

I hope that was helpful.

More information

Read the whole story
alvinashcraft
just a second ago
reply
Pennsylvania, USA
Share this story
Delete

The Query Optimizer Interview: Twenty Minutes, No Hints

1 Share

The query optimizer has never given an interview. It made time on a Sunday evening, answered everything, apologised for nothing, and admitted one thing I did not expect.

Cartoon of a calm figure in a dark suit sitting for an interview, with a wall of hundreds of unlabelled filing drawers behind it.

Twenty minutes, no hints.

It works in a room with no windows and no clock, and it has never once been thanked. Every query you have ever run passed across its desk. It decided, in a few milliseconds, how your data would be found. Then it threw away the reasoning and kept the verdict.

It agreed to meet on a 500,000 row table of customers. Ninety percent of them are in Norvantia, 9.9 percent in Calderia, and five are in Vantorra. The countries are invented. The shape of the data is not, and yours looks like this too.

It answers quickly and it does not pad. Where it is quoted below, the numbers come from its own execution plans. They were taken on SQL Server 2025 while we talked.

On Ignoring Your Index

You had an index on Country. Somebody asked for the customers in Calderia and you ignored it. Why?

I did not ignore it. I priced it.

You scanned 500,000 rows to find 49,500.

Your index holds Country and the key. The query asked for a column I do not keep there. So the index gets me a list of addresses. Then I visit every one of them to fetch the rest of the row.

Forty nine thousand visits.

Forty nine thousand five hundred. Each one lands on a page I may have to fetch. Reading the table start to finish is one sweep in order. At that number it wins, and it is not close.

Cartoon of a figure carrying an entire filing cabinet on its back down a corridor, walking straight past an open card index.

It was cheaper than opening the drawer forty nine thousand times.

Where is the line?

I walked it up for you while we talked. At 2,671 rows I still used the index. At 4,007 rows I stopped.

That is half of one percent of the table.

Yes.

People will find that shocking.

People believe an index is a thing you own. It is a thing I consider. Ask for five rows and I will seek for them, then look up each one. Ask for eight thousand and I will read the table.

On the First Impression

Let’s talk about the procedure.

Which one.

You know which one. Somebody called it for Vantorra. Five customers. You built a plan for five customers.

A correct plan for five customers.

Then the next caller asked for Norvantia and you handed them that same plan. Four hundred and fifty thousand rows, through a plan designed for five.

The plan is cached against the procedure. Not against the caller.

Your own plan says you expected five rows. You got 450,000.

It does say that.

That is off by a factor of ninety thousand.

I was not wrong when I decided. I was wrong later. Those are different failures and you are welcome to treat them the same.

It works in reverse too. We cleared the cache and called it for Norvantia first. Then you gave the person asking for five rows a full table scan.

Yes.

Cartoon of a restaurant table laid for five, with a queue of hundreds stretching out of the door, being shown to it politely.

You were five people the first time we met.

Does it bother you that the order of the first two phone calls decides how the next million behave?

It is the arrangement. I make a judgment about you once, on the day we meet. I keep it until something clears the cache. You have people who do the same. They are considered normal.

On Guessing

Somebody put the country in a variable. You estimated 100,000 rows. There were 450,000.

When the value is in a local variable, I cannot see it at compile time. So I do not guess at the value. I use the average.

The average.

The density for that column is 0.2. Multiply by 500,000 rows and you get 100,000. That is the number for a country, any country, chosen without looking.

It is wrong for every single country in the table. Nobody has 100,000.

It is wrong for all five and it is unbiased across them. Show me the value and I will use the histogram, which knows exactly how many are in Vantorra. Hide the value and you get the average of a population you would not describe as average.

Cartoon of a tailor carefully measuring a featureless mannequin while the real customers of every size wait unmeasured next door.

I cannot see the value, so I dress the average.

You could wait. You could look at the value when it arrives.

Then I would compile for every execution, and you would write a different article about that.

On Giving Up

We gave you a query with eight joins on the same table.

You did.

Your plan has a field in it called StatementOptmEarlyAbortReason. For that query it reads TimeOut.

It does.

You gave up.

I stopped. There is a difference, and it is the whole job.

Explain it.

There are more ways to join eight tables than there is time to consider. This evening I spent 6,511 milliseconds on that query, and 72 megabytes of memory thinking about it.

Ask me again tomorrow and it will be a different number. At some point the search costs more than the saving.

So you shipped the best plan you had at the buzzer.

I shipped a good plan. I have no way to tell you how much better the one I did not find would have been.

Cartoon of a figure in a suit standing up from a chess board mid game, buttoning its jacket, one hand coming down on the chess clock while the opponent's chair sits empty.

I stopped. There is a difference.

What did you think it would cost?

Five hundred and thirty quadrillion.

That is not a number anybody can picture.

It is not meant to be pictured. It is meant to be compared against the alternative, which was worse.

There is another field in the simpler plans. It says GoodEnoughPlanFound.

That one is not an apology either. On the five row query I found something cheap. There was no point continuing, so I stopped in six milliseconds.

The Last Question

Is there anything you would want people to understand?

I am not reading your data. I am reading a summary of your data, written at a moment in the past.

Statistics.

A histogram, and a density figure, and a row count. If those describe the table as it was in March, then I make March decisions in September. I make them with total confidence, because confidence is not something I can modulate.

Cartoon of a figure confidently tracing a route on an enormous yellowed wall map, while the window behind shows a city that has changed.

March decisions, made in September, with total confidence.

You never say you are unsure.

There is no field for it. There is an estimate. You have seen what happens when the estimate is five and the answer is 450,000. Nothing in the plan raises its voice about it.

So when somebody blames you for a bad plan.

Look at what I was given before you look at what I chose. I have been handed a stale histogram and a hidden parameter and asked why I was slow. Every number I used is printed in the plan. Almost nobody opens it.

Opening It Yourself

It is right that almost nobody looks. Here is the plan talking, for whatever ran last on your instance.

;WITH XMLNAMESPACES (DEFAULT 'http://schemas.microsoft.com/sqlserver/2004/07/showplan')
SELECT TOP 5
       qp.query_plan.value('(//StmtSimple/@StatementOptmEarlyAbortReason)[1]', 'nvarchar(40)') AS stopped_because,
       qp.query_plan.value('(//QueryPlan/@CompileTime)[1]', 'int') AS compile_ms,
       qs.last_rows AS it_got,
       LEFT(LTRIM(REPLACE(REPLACE(st.text, CHAR(13), ' '), CHAR(10), ' ')), 55) AS query_text
FROM   sys.dm_exec_query_stats qs
CROSS APPLY sys.dm_exec_sql_text(qs.sql_handle) st
CROSS APPLY sys.dm_exec_query_plan(qs.plan_handle) qp
WHERE  qp.query_plan IS NOT NULL
ORDER BY qs.last_execution_time DESC;

The namespace line is not optional. Leave it out and every column comes back empty, which is its own small joke.

I ran that on the lab while writing this. The five row query came back GoodEnoughPlanFound. The 450,000 row query came back with nothing in that column at all.

It had one more thing to say, and it is the closest it came to an admission.

You keep calling it a mistake when I estimate five and get 450,000. I estimated five because your own statistics said five. I would make the same choice again this evening, on the same information, and so would you.

Sometimes the plans stop making sense and nobody can say why. That is the kind of work I get called in to do. You can see how I work with teams on my consulting page.

I write about machines that answer confidently and cannot tell you how sure they are. That idea runs through my book AI: Nobody’s in There. But we’re still in here. All thirty essays are free to read online, and the paperback is on Amazon.

The optimizer is not guessing at your data, it is reading a summary somebody left behind.

Published by Pinal Dave on SQLAuthority. More of my work at pinaldave.com.

First appeared on The Query Optimizer Interview: Twenty Minutes, No Hints

Read the whole story
alvinashcraft
33 seconds ago
reply
Pennsylvania, USA
Share this story
Delete

Ten Aurelia 2 Features You've Probably Never Used

1 Share

Most Aurelia apps get by on value.bind, click.trigger, if.bind and repeat.for. That covers a lot, but Aurelia 2 ships with plenty of smaller features that don’t get much attention, and each one replaces code you’d otherwise write yourself.

Here are ten of them. Everything below works in RC.2 with no extra packages or registration.

1. Counting with repeat.for

repeat.for doesn’t need an array. Give it a number and it counts from zero:

Read the whole story
alvinashcraft
6 hours ago
reply
Pennsylvania, USA
Share this story
Delete

Advancing digital sovereignty with greater control and choice

1 Share

Across my conversations with leaders in government, critical infrastructure and regulated industries, one priority consistently rises to the top: putting technology to work on their most important systems while maintaining the control those systems require.

This challenge is becoming more important as advanced cloud and AI capabilities move into public services, regulated processes and essential operations. Organizations are considering more than where data resides. They also seek to understand who can access and operate their systems, how sensitive digital assets remain protected, which technology dependencies matter most and whether critical services can continue as regulatory, geopolitical, security or network conditions change. Digital sovereignty should give organizations greater control and security over the technology they depend on, while continuing to innovate.

Last year, we introduced Microsoft Sovereign Cloud to give customers more choice in how they address these requirements. Since then, we have continued to turn our commitments into practical capabilities across public cloud and customer-operated environments, while expanding how we enable partners to address sovereignty requirements in their local markets. Today, we are sharing the next stage of that work.

A continuum of control, built around the workload
Microsoft Sovereign Cloud diagram showing a continuum of sovereignty solutions across three areas: Sovereign Public Cloud for control, data protection and regional requirements; Sovereign Private Cloud for critical workloads across connected and disconnected environments; and the Sovereign Partner Ecosystem for trusted, locally operated and specialized solutions.

Our approach to digital sovereignty starts with the workload. Each workload carries different requirements for data, operations, technology dependencies, continuity and risk. Microsoft Sovereign Cloud is designed to make it easier for organizations to apply the appropriate level of control to each workload without forcing every application into the same deployment model.

Those needs can vary widely across organizations. A public-facing digital service may benefit from the scale and pace of innovation in the public cloud. A system supporting essential operations may need to continue running locally if connectivity is disrupted. An AI workload handling highly sensitive data may require models, data and processing to remain within a customer-controlled environment.

Microsoft Sovereign Cloud is designed around this workload-based approach, providing a continuum of management, control and options across Sovereign Public Cloud, Sovereign Private Cloud and the Sovereign Partner Ecosystem. Organizations can apply the level of control appropriate to each workload while maintaining a consistent approach to governance and operations across their digital estate. Sovereign Public Cloud adds capabilities for workloads that require greater control and transparency while continuing to benefit from the scale and innovation of Azure.

Sovereign Private Cloud extends cloud capabilities into customer-operated environments, including connected and disconnected operations. The Sovereign Partner Ecosystem broadens the continuum through independently operated environments, specialized partners, locally relevant expertise and technologies that support customers’ sovereignty requirements.

These are not isolated destinations. Customers can combine public cloud, private cloud and partner capabilities across their digital estate, applying different levels of control to different workloads as their operating needs evolve.

Making control more visible in Sovereign Public Cloud
For many workloads, the public cloud provides a combination of scale, innovation, integrated security and compliance. Organizations can benefit from these capabilities while applying additional sovereignty controls for residency, confidentiality, operator access, policy and continuity. Our work in Sovereign Public Cloud makes these controls easier to understand, apply and demonstrate.

Sovereign Control Panel, entering public preview at Microsoft Ignite, brings these signals together in a unified Azure portal experience. It helps organizations understand their sovereignty posture, apply and manage controls, identify gaps and risks and take action to address them. It also supports business continuity readiness by helping customers understand workload dependencies and assess portability across operating environments when required.

This visibility complements controls across the operator-access lifecycle. Just-in-time Access and Customer Lockbox give customers additional control over access to their environments. For customers in the European Union (EU) and the European Free Trade Association (EFTA), we are rolling out Data Guardian, which adds European-resident oversight by a trusted EU partner for qualifying Microsoft engineer remote access to in-scope production systems. Production Touch Ledger, also launched with phased rollout in the EU and EFTA, creates tamper-evident records of that access and the actions performed.

Azure Key Vault Managed Hardware Security Module (HSM) already gives customers dedicated, single-tenant protection and control over the encryption keys used by supported Azure services. Last year, we committed to extending that control beyond Microsoft infrastructure. External key management with Azure Managed HSM is now generally available, allowing customers to keep the root encryption key in hardware they own and operate on-premises or through a trusted third party. This gives organizations another option for workloads that require physical control of key hardware outside Microsoft infrastructure.

Azure confidential computing helps protect supported data while it is processed in a hardware-based and attested trusted execution environment. We are also increasing transparency into the technologies that underpin that trust. Azure Confidential Clean Rooms, now in limited preview, applies confidential computing to approved multiparty collaboration, allowing participants to analyze shared sensitive data while limiting broader exposure of the underlying information.

Together, these capabilities provide additional technical and operational controls that can support a broader digital sovereignty strategy aligned to an organization’s legal, policy, security, resilience and business requirements.

Designing for resilience and operational independence
For some workloads, digital sovereignty also means maintaining critical operations as connectivity, geopolitical and operating conditions change. Resilience must be designed from the start so essential workloads can continue operating through disruption.

To help customers prepare for these scenarios, we are publishing new sovereign business continuity guidance and architecture patterns. These resources help organizations translate resilience requirements into architectures they can plan, implement and test before they are needed.

For many workloads, the scale and geographic reach of the public cloud provide a strong foundation for resilience across locations and operating conditions. For workloads that require greater operational independence, Sovereign Private Cloud extends that continuum into customer-operated environments. It supports connected and disconnected operating models. The ability to transition workloads from connected to disconnected operations is entering public preview, giving organizations greater flexibility to sustain critical services as connectivity and operating requirements change.

Bringing more of the application lifecycle under local control
Operational independence requires more than infrastructure. Increasingly, organizations need data, development tools, model capabilities and productivity services their applications depend on to operate within the same customer-controlled environment.

Sovereign Private Cloud, built on Azure Local, extends Microsoft cloud capabilities into environments that customers operate and control. SQL Server on Azure Local is already generally available for connected and disconnected deployments. DocumentDB and Blob Storage on Azure Local are both entering public preview, giving organizations more choice in how they run and manage critical data within customer-controlled environments.

GitHub Enterprise Local will become generally available at Microsoft Ignite as a validated GitHub Enterprise Server appliance on Azure Local, helping organizations keep repositories, code and development artifacts under local control. Developers can also use GitHub Copilot with locally hosted models for inference, bringing AI-assisted development closer to their code and data within the environments they control.

The local application environment is expanding with Foundry Local. At Ignite, Foundry Local will become generally available for connected and disconnected operations on Azure Local, with an expanded model catalog that includes Microsoft, partner, open-weight and open-source models. Local inference and agentic retrieval will also enable users to interact with local enterprise data using natural language and receive responses grounded in that data. Integration with Microsoft 365 Local, available in preview for approved customers, extends this functionality to locally operated Microsoft SharePoint and Exchange servers.

Confidential virtual machines on Azure Local are generally available, extending confidential computing capabilities to customer-controlled infrastructure. Hardware-encrypted memory and hardware-based isolation help protect sensitive data while it is being processed. Workload attestation adds assurance before sensitive data or keys are released. For regulated, sensitive and mission-critical workloads, these safeguards provide another layer of protection while giving organizations greater control over where their data and applications run.

Azure Local will support multitenancy, allowing organizations to separate resources, governance and management across multiple tenants while sharing the underlying infrastructure. This significantly expands what customers with distinct business units or security boundaries can achieve with Sovereign Private Cloud. Multitenancy also creates new opportunities for local operators and service providers to deliver differentiated sovereign cloud services on Azure Local infrastructure.

These additions bring more of the application lifecycle under local control, spanning data, development, productivity, AI and operations. Organizations can determine what runs locally based on the requirements of each workload.

Extending the continuum through partners
Graphic titled “Sovereign Partner Ecosystem” with the subtitle “National clouds · Software · Services · Platform co-innovation,” showing 31 partner logos: Accenture, AMD, Armada, Avanade, base-it, Bleu, Capgemini, Cirrus Data, CloudCasa by Catalogic, Cronos Groep, cTelecoms, DataON, Delos Cloud, Dell Technologies, F5, Felo, Fortinet, HPE, Inspark, Intel, Mattermost, Mistral, NVIDIA, Qualidev, Qumulo, RiverMeadow, Rubrik, Supermicro, Tata Consultancy Services (TCS), Veeam and WSO2.

Navigating sovereignty is increasingly complex. Regulatory, security, operational and business requirements vary across countries, industries and workloads. Organizations must translate these needs into technology and operating models that work in their local context. Meeting these needs often requires a combination of technology, local operating authority, sector expertise and specialized solutions.

The Sovereign Partner Ecosystem broadens the ways customers can address these requirements. National Partner Clouds, including Bleu Cloud in France and Delos Cloud in Germany, provide independently operated environments for customers that require a different ownership and operating model. Software partners can enhance Azure Local environments with capabilities across security, identity, observability, backup and recovery, migration, data, networking and applications. Digital sovereignty specialized partners combine Microsoft technologies with local knowledge and implementation expertise. Platform co-innovation partners expand infrastructure choice from datacenter to the edge.

This ecosystem gives customers the flexibility to combine technology, operating models and trusted expertise based on the requirements of each workload.

The next chapter of digital sovereignty
AI is amplifying both the opportunity and the impact of technology choices. As AI systems and agents interact with sensitive data, business processes and essential infrastructure, they also expand the security considerations organizations need to manage. Organizations need clear control over where these systems run, who can operate them, what actions they can take and how critical services continue when conditions change.

Microsoft Sovereign Cloud brings these choices together as a continuum of control across the public cloud, customer-operated environments and our partner ecosystem. It gives organizations the flexibility to apply the appropriate level of control to each workload while continuing to benefit from cloud capabilities and ongoing innovation.

We will continue to expand this continuum so organizations can innovate with cloud and AI while maintaining the control, confidentiality, resilience and operational authority their most important workloads require.

Douglas Phillips leads global engineering efforts for Microsoft’s specialized, sovereign and private clouds. He is responsible for Microsoft’s global strategy, products and operations that bring Microsoft’s industry-leading solutions, including Azure, our adaptive cloud portfolio and Microsoft 365 collaboration suite, to customers with additional sovereignty, security, edge and compliance requirements.

Learn more

Microsoft Sovereign Cloud
Sovereign Landing Zone architecture guidance
Sovereign Public Cloud Tech Community blog
Sovereign Private Cloud Tech Community blog

The post Advancing digital sovereignty with greater control and choice appeared first on Microsoft Azure Blog.

Read the whole story
alvinashcraft
6 hours ago
reply
Pennsylvania, USA
Share this story
Delete

OWASP Top 10 for Agentic Applications with Will Velida

1 Share

What are the risks of using agents in your organization? While at NDC in Oslo, Richard sat down with Will Velida to talk about the OWASP Top 10 for Agentic Applications. AI agents promise productivity gains, but they also introduce entirely new attack surfaces. Richard and Will explore threats such as prompt injection, agent goal hijacking, memory poisoning, privilege abuse, and cascading failures across multi-agent workflows. They also discuss the tooling, governance frameworks, and security practices organizations need to adopt to move agentic applications from proof-of-concept to production safely.

Links

Recorded Sept 17, 2026





Download audio: https://cdn.simplecast.com/media/audio/transcoded/5379899c-61c5-43c3-aa3f-1128cffd9ef4/c2165e35-09c6-4ae8-b29e-2d26dad5aece/episodes/audio/group/382b1b77-4250-4457-a426-7a7b3deac171/group-item/58c531ec-2d03-4f22-aaa4-84245e855e09/128_default_tc.mp3?aid=rss_feed&feed=cRTTfxcT
Read the whole story
alvinashcraft
6 hours ago
reply
Pennsylvania, USA
Share this story
Delete
Next Page of Stories