Passa al contenuto principale

Blazor WASM/PWA - Configurazione AppSettings per più clienti

Obiettivo

Gestire pubblicazioni diverse della stessa app Blazor WebAssembly/PWA per più clienti, evitando di modificare manualmente appsettings.json dopo la pubblicazione.

Nelle app Blazor WASM/PWA i file pubblicati vengono gestiti anche dal service worker e dal manifest degli asset:

service-worker-assets.js


Se si modifica manualmente un file statico dopo la pubblicazione, ad esempio appsettings.json, si possono creare problemi di cache, integrità o caricamento della PWA.

La soluzione corretta è:

1. mantenere un appsettings.json comune
2. creare un file appsettings per ogni cliente
3. tenere i file cliente fuori da wwwroot
4. usare un profilo di pubblicazione per cliente
5. fare copiare al .csproj solo il file del cliente pubblicato



1. Struttura cartelle consigliata

Nel progetto Blazor WASM, ad esempio Soluziona.Apps.Magazzino, usare questa struttura:

Soluziona.Apps.Magazzino
├── SettingClienti
│   ├── Comside
│   │   └── appsettings.Comside.json
│   └── Demo
│       └── appsettings.Demo.json
│
├── Properties
│   └── PublishProfiles
│       ├── Comside.pubxml
│       └── Demo.pubxml
│
└── wwwroot
    ├── appsettings.json
    ├── appsettings.Development.json
    ├── index.html
    ├── manifest.webmanifest
    ├── service-worker.js
    └── ...


Regola

Dentro wwwroot devono rimanere solo:

appsettings.json
appsettings.Development.json


I file dei clienti devono stare fuori da wwwroot, dentro:

SettingClienti\<NomeCliente>\appsettings.<NomeCliente>.json


Esempio:

SettingClienti\Comside\appsettings.Comside.json
SettingClienti\Demo\appsettings.Demo.json


Non mettere dentro wwwroot:

appsettings.Comside.json
appsettings.Demo.json


altrimenti rischiano di essere pubblicati tutti insieme.


2. appsettings.json comune

Il file:

wwwroot\appsettings.json


deve contenere solo i valori comuni a tutti i clienti.

Esempio:

{
  "App": {
    "NomeApp": "Liste di Carico",
    "SottoTitoloApp": "Gestione preparazione ordini",
    "Versione": "1.0.6",
    "Build": "2026.09.09.006",
    "Ambiente": "Production",
    "PaginaIniziale": "/calendario"
  },
  "Api": {
    "TimeoutSecondi": 30
  },
  "Offline": {},
  "SyncListeDiCarico": {
    "IntervalloSecondi": 30,
    "GiorniIndietro": 15,
    "GiorniAvanti": 15,
    "EseguiAllAvvio": true
  }
}


Nota

In questo file può mancare:

"BaseUrl"


perché viene valorizzato dal file cliente, ad esempio:

appsettings.Comside.json


A runtime Blazor caricherà:

appsettings.json
+
appsettings.Comside.json



3. appsettings.Development.json

Il file:

wwwroot\appsettings.Development.json


viene usato per lo sviluppo locale.

Esempio:

{
  "App": {
    "IdApplicazione": "17630f17-7626-416f-961b-486fbd64fd4f",
    "CodiceApplicazione": "APPLISTEDICARICO",
    "CodiceAzienda": "3",
    "Ambiente": "Development"
  },
  "Api": {
    "BaseUrl": "https://localhost:7021/",
    "TimeoutSecondi": 30
  }
}


Questo file non deve essere pubblicato in produzione.


4. AppSettings per cliente

Demo

Percorso:

SettingClienti\Demo\appsettings.Demo.json


Contenuto:

{
  "App": {
    "IdApplicazione": "17630f17-7626-416f-961b-486fbd64fd4f",
    "CodiceApplicazione": "APPLISTEDICARICO",
    "CodiceAzienda": "3",
    "Ambiente": "Production"
  },
  "Api": {
    "BaseUrl": "https://soluzionacoreapi.soluziona.cloud:443/",
    "TimeoutSecondi": 30
  }
}


Comside

Percorso:

SettingClienti\Comside\appsettings.Comside.json


Contenuto:

{
  "App": {
    "IdApplicazione": "90208117-20b4-446c-a771-42bce3fb63c5",
    "CodiceApplicazione": "ListeDiCarico",
    "CodiceAzienda": "59",
    "Ambiente": "Production"
  },
  "Api": {
    "BaseUrl": "https://192.168.232.122:4433/",
    "TimeoutSecondi": 30
  }
}



5. Modifica del .csproj

Aprire il file .csproj del progetto Blazor WASM, ad esempio:

Soluziona.Apps.Magazzino.csproj


La configurazione deve:

1. escludere SettingClienti dagli item automatici del SDK
2. escludere eventuali appsettings ambiente da wwwroot
3. copiare il file cliente corretto dentro wwwroot prima della build/publish
4. includere esplicitamente il file copiato come Content


5.1 Esclusione file cliente e appsettings ambiente

Nel primo PropertyGroup aggiungere:

<DefaultItemExcludes>$(DefaultItemExcludes);SettingClienti\**\*;wwwroot\appsettings.*.json</DefaultItemExcludes>


Esempio:

<PropertyGroup>
  <TargetFramework>net10.0</TargetFramework>
  <Nullable>enable</Nullable>
  <ImplicitUsings>enable</ImplicitUsings>
  <OverrideHtmlAssetPlaceholders>true</OverrideHtmlAssetPlaceholders>
  <ServiceWorkerAssetsManifest>service-worker-assets.js</ServiceWorkerAssetsManifest>

  <!-- Esclude i file cliente dagli item automatici del .NET SDK -->
  <DefaultItemExcludes>$(DefaultItemExcludes);SettingClienti\**\*;wwwroot\appsettings.*.json</DefaultItemExcludes>
</PropertyGroup>


Questa riga esclude:

SettingClienti\...
wwwroot\appsettings.Demo.json
wwwroot\appsettings.Comside.json
wwwroot\appsettings.Development.json


Non esclude:

wwwroot\appsettings.json


perché il file comune deve sempre essere pubblicato.


5.2 Reincludere appsettings.Development.json

Poiché wwwroot\appsettings.*.json viene escluso dagli item automatici, appsettings.Development.json va reincluso esplicitamente, ma senza pubblicarlo.

<Content Include="wwwroot\appsettings.Development.json">
  <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
  <CopyToPublishDirectory>Never</CopyToPublishDirectory>
</Content>



5.3 Target per preparare l’appsettings cliente

Aggiungere questo target prima di:

</Project>



<Target Name="PreparaAppSettingsCliente"
        BeforeTargets="AssignTargetPaths"
        Condition="'$(WasmApplicationEnvironmentName)' != ''
                and '$(WasmApplicationEnvironmentName)' != 'Development'
                and Exists('$(MSBuildProjectDirectory)\SettingClienti\$(WasmApplicationEnvironmentName)\appsettings.$(WasmApplicationEnvironmentName).json')">

  <Message Importance="High"
           Text="Preparo appsettings cliente: SettingClienti\$(WasmApplicationEnvironmentName)\appsettings.$(WasmApplicationEnvironmentName).json" />

  <!-- Elimina eventuali appsettings cliente generati da build precedenti -->
  <ItemGroup>
    <AppSettingsClienteInWwwRoot Include="$(MSBuildProjectDirectory)\wwwroot\appsettings.*.json"
                                 Exclude="$(MSBuildProjectDirectory)\wwwroot\appsettings.Development.json" />
  </ItemGroup>

  <Delete Files="@(AppSettingsClienteInWwwRoot)" />

  <!-- Copia il file cliente dentro wwwroot prima della build/publish -->
  <Copy SourceFiles="$(MSBuildProjectDirectory)\SettingClienti\$(WasmApplicationEnvironmentName)\appsettings.$(WasmApplicationEnvironmentName).json"
        DestinationFiles="$(MSBuildProjectDirectory)\wwwroot\appsettings.$(WasmApplicationEnvironmentName).json"
        SkipUnchangedFiles="false" />

  <!-- Include esplicitamente l'appsettings cliente generato come Content -->
  <ItemGroup>
    <Content Include="wwwroot\appsettings.$(WasmApplicationEnvironmentName).json">
      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
      <CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
    </Content>
  </ItemGroup>

</Target>



6. .csproj completo finale

<Project Sdk="Microsoft.NET.Sdk.BlazorWebAssembly">

  <PropertyGroup>
    <TargetFramework>net10.0</TargetFramework>
    <Nullable>enable</Nullable>
    <ImplicitUsings>enable</ImplicitUsings>
    <OverrideHtmlAssetPlaceholders>true</OverrideHtmlAssetPlaceholders>
    <ServiceWorkerAssetsManifest>service-worker-assets.js</ServiceWorkerAssetsManifest>

    <!-- Esclude i file cliente dagli item automatici del .NET SDK -->
    <DefaultItemExcludes>$(DefaultItemExcludes);SettingClienti\**\*;wwwroot\appsettings.*.json</DefaultItemExcludes>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="DevExpress.Blazor" Version="26.1.4" />
    <PackageReference Include="DevExpress.Blazor.Themes" Version="26.1.4" />
    <PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="10.0.11" />
    <PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.DevServer" Version="10.0.11" PrivateAssets="all" />
    <PackageReference Include="Microsoft.Extensions.Http" Version="10.0.11" />
    <PackageReference Include="System.Security.Cryptography.Xml" Version="10.0.11" />
  </ItemGroup>

  <ItemGroup>
    <ProjectReference Include="..\Soluziona.Apps.UI\Soluziona.Apps.UI.csproj" />
    <ProjectReference Include="..\Soluziona.Apps.Utility\Soluziona.Apps.Core.csproj" />
    <ProjectReference Include="..\SoluzionaApps.ApiClient\Soluziona.Apps.ApiClient.csproj" />
  </ItemGroup>

  <ItemGroup>
    <ServiceWorker Include="wwwroot\service-worker.js" PublishedContent="wwwroot\service-worker.published.js" />
  </ItemGroup>

  <ItemGroup>
    <Content Update="wwwroot\web.config">
      <CopyToOutputDirectory>Always</CopyToOutputDirectory>
    </Content>

    <!-- Development escluso dai Content automatici, quindi lo reincludiamo esplicitamente -->
    <Content Include="wwwroot\appsettings.Development.json">
      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
      <CopyToPublishDirectory>Never</CopyToPublishDirectory>
    </Content>
  </ItemGroup>

  <ItemGroup>
    <Folder Include="Properties\PublishProfiles\" />
  </ItemGroup>

  <Target Name="PreparaAppSettingsCliente"
          BeforeTargets="AssignTargetPaths"
          Condition="'$(WasmApplicationEnvironmentName)' != ''
                  and '$(WasmApplicationEnvironmentName)' != 'Development'
                  and Exists('$(MSBuildProjectDirectory)\SettingClienti\$(WasmApplicationEnvironmentName)\appsettings.$(WasmApplicationEnvironmentName).json')">

    <Message Importance="High"
             Text="Preparo appsettings cliente: SettingClienti\$(WasmApplicationEnvironmentName)\appsettings.$(WasmApplicationEnvironmentName).json" />

    <!-- Elimina eventuali appsettings cliente generati da build precedenti -->
    <ItemGroup>
      <AppSettingsClienteInWwwRoot Include="$(MSBuildProjectDirectory)\wwwroot\appsettings.*.json"
                                   Exclude="$(MSBuildProjectDirectory)\wwwroot\appsettings.Development.json" />
    </ItemGroup>

    <Delete Files="@(AppSettingsClienteInWwwRoot)" />

    <!-- Copia il file cliente dentro wwwroot prima della build/publish -->
    <Copy SourceFiles="$(MSBuildProjectDirectory)\SettingClienti\$(WasmApplicationEnvironmentName)\appsettings.$(WasmApplicationEnvironmentName).json"
          DestinationFiles="$(MSBuildProjectDirectory)\wwwroot\appsettings.$(WasmApplicationEnvironmentName).json"
          SkipUnchangedFiles="false" />

    <!-- Include esplicitamente l'appsettings cliente generato come Content -->
    <ItemGroup>
      <Content Include="wwwroot\appsettings.$(WasmApplicationEnvironmentName).json">
        <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
        <CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
      </Content>
    </ItemGroup>

  </Target>

</Project>

Nota importante per Git

Il target copia automaticamente il file cliente dentro wwwroot.

Esempio:

SettingClienti\Comside\appsettings.Comside.json
→ wwwroot\appsettings.Comside.json

Questo file in wwwroot è generato automaticamente e non deve essere committato.

Aggiungere al .gitignore:

# AppSettings cliente generati automaticamente dal publish
**/wwwroot/appsettings.*.json

# Mantiene versionato appsettings.Development.json
!**/wwwroot/appsettings.Development.json

wwwroot\appsettings.json non viene escluso da questa regola, quindi rimane versionato normalmente.

7. Profili di pubblicazione

I profili vanno creati da Visual Studio con:

Tasto destro sul progetto
→ Publish
→ New profile
→ Folder


Dopo la creazione, modificare i file .pubxml.


Profilo Demo

Percorso:

Properties\PublishProfiles\Demo.pubxml


Contenuto:

<?xml version="1.0" encoding="utf-8"?>
<Project>
  <PropertyGroup>
    <DeleteExistingFiles>true</DeleteExistingFiles>
    <ExcludeApp_Data>false</ExcludeApp_Data>
    <LaunchSiteAfterPublish>true</LaunchSiteAfterPublish>

    <LastUsedBuildConfiguration>Release</LastUsedBuildConfiguration>
    <LastUsedPlatform>Any CPU</LastUsedPlatform>

    <PublishProvider>FileSystem</PublishProvider>
    <PublishUrl>C:\Repos\Pubblicati\AppListeDiCarico\Demo</PublishUrl>
    <WebPublishMethod>FileSystem</WebPublishMethod>
    <_TargetId>Folder</_TargetId>

    <WasmApplicationEnvironmentName>Demo</WasmApplicationEnvironmentName>
  </PropertyGroup>
</Project>



Profilo Comside

Percorso:

Properties\PublishProfiles\Comside.pubxml


Contenuto:

<?xml version="1.0" encoding="utf-8"?>
<Project>
  <PropertyGroup>
    <DeleteExistingFiles>true</DeleteExistingFiles>
    <ExcludeApp_Data>false</ExcludeApp_Data>
    <LaunchSiteAfterPublish>true</LaunchSiteAfterPublish>

    <LastUsedBuildConfiguration>Release</LastUsedBuildConfiguration>
    <LastUsedPlatform>Any CPU</LastUsedPlatform>

    <PublishProvider>FileSystem</PublishProvider>
    <PublishUrl>C:\Repos\Pubblicati\AppListeDiCarico\Comside</PublishUrl>
    <WebPublishMethod>FileSystem</WebPublishMethod>
    <_TargetId>Folder</_TargetId>

    <WasmApplicationEnvironmentName>Comside</WasmApplicationEnvironmentName>
  </PropertyGroup>
</Project>



8. Proprietà fondamentale: WasmApplicationEnvironmentName

La riga più importante del profilo è questa:

<WasmApplicationEnvironmentName>Comside</WasmApplicationEnvironmentName>


oppure:

<WasmApplicationEnvironmentName>Demo</WasmApplicationEnvironmentName>


Questa proprietà decide quale ambiente Blazor deve usare.

Esempio:

WasmApplicationEnvironmentName = Comside
→ carica appsettings.json
→ carica appsettings.Comside.json


WasmApplicationEnvironmentName = Demo
→ carica appsettings.json
→ carica appsettings.Demo.json


Il valore deve combaciare esattamente con:

nome cartella cliente
nome file appsettings


Esempio corretto:

Comside
→ SettingClienti\Comside\appsettings.Comside.json


Esempio errato:

COMSIDE
→ SettingClienti\Comside\appsettings.Comside.json



9. Nota su App:Ambiente

Dentro gli appsettings cliente si può lasciare:

"Ambiente": "Production"


Questo valore è una configurazione applicativa interna.

Non decide quale file appsettings.*.json viene caricato.

Il file caricato viene deciso da:

<WasmApplicationEnvironmentName>Comside</WasmApplicationEnvironmentName>


non da:

"Ambiente": "Production"