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.

Questo è importante perché nelleNelle app Blazor WASM/PWA i file pubblicati vengono inseritigestiti nelanche dal service worker e dal manifest degli asset, ad esempio:asset:

service-worker-assets.js

Se si modifica manualmente un file statico dopo la pubblicazione, comead esempio appsettings.json, gli hashsi possono noncreare corrispondere più e la PWA può generare erroriproblemi di integrità/cache.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/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/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. SettingsAppSettings 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

5.1 Escludere SettingClienti dagli item automatici

Nel primo PropertyGroup aggiungere:

<DefaultItemExcludes>$(DefaultItemExcludes);SettingClienti\**\*</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\**\*</DefaultItemExcludes>
</PropertyGroup>

Questa riga impedisce al SDK Blazor di trattare automaticamente i file cliente come asset statici.


5.2 Non usare Content Include diretto

Non usare questa forma:

<Content Include="SettingClienti\Comside\appsettings.Comside.json" />

perché può generare errori del tipo:

More than one compatible asset found for target path 'appsettings.Comside#[.{fingerprint}]?.json'


5.3 Aggiungere il Target di pubblicazione

Aggiungere questo blocco prima di:

</Project>

<ItemGroup>Target <NoneName="AggiungiAppSettingsClienteAllaPubblicazione"
        Include=BeforeTargets="SettingClienti\**\*.json" />
</ItemGroup>

<ItemGroupComputeFilesToPublish"
        Condition="'$(WasmApplicationEnvironmentName)' != ''
                and '$(WasmApplicationEnvironmentName)' != 'Development'
                and Exists('$(MSBuildProjectDirectory)\SettingClienti\$(WasmApplicationEnvironmentName)\appsettings.$(WasmApplicationEnvironmentName).json')">

  <ContentMessage Include=Importance="High"
           Text="Pubblico appsettings cliente: SettingClienti\$(WasmApplicationEnvironmentName)\appsettings.$(WasmApplicationEnvironmentName).json" Link=/>

  <ItemGroup>
    <ResolvedFileToPublish Include="$(MSBuildProjectDirectory)\SettingClienti\$(WasmApplicationEnvironmentName)\appsettings.$(WasmApplicationEnvironmentName).json">
      <RelativePath>wwwroot\appsettings.$(WasmApplicationEnvironmentName).json"json</RelativePath>
      CopyToOutputDirectory="PreserveNewest"<CopyToPublishDirectory>Always</CopyToPublishDirectory>
    CopyToPublishDirectory="PreserveNewest" </ResolvedFileToPublish>
  </ItemGroup>

</Target>

Cosa
fa

6. questo.csproj blocco

completo

Quandodi si pubblica con ambiente:

esempio
Comside<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\**\*</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>

    <Content Update="wwwroot\appsettings.Development.json">
      <CopyToPublishDirectory>Never</CopyToPublishDirectory>
    </Content>
  </ItemGroup>

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

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

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

    <ItemGroup>
      <ResolvedFileToPublish Include="$(MSBuildProjectDirectory)\SettingClienti\$(WasmApplicationEnvironmentName)\appsettings.$(WasmApplicationEnvironmentName).json">
        <RelativePath>wwwroot\appsettings.$(WasmApplicationEnvironmentName).json</RelativePath>
        <CopyToPublishDirectory>Always</CopyToPublishDirectory>
      </ResolvedFileToPublish>
    </ItemGroup>

  </Target>

</Project>

il .csproj prende:

SettingClienti\Comside\appsettings.Comside.json

e lo pubblica come:

wwwroot\appsettings.Comside.json

Quando si pubblica con ambiente:

Demo

il .csproj prende:

SettingClienti\Demo\appsettings.Demo.json

e lo pubblica come:

wwwroot\appsettings.Demo.json


6.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>


7.8. Proprietà fondamentale: WasmApplicationEnvironmentName

La riga più importante del profilo è questa:

<WasmApplicationEnvironmentName>Comside</WasmApplicationEnvironmentName>

oppure:

<WasmApplicationEnvironmentName>Demo</WasmApplicationEnvironmentName>

Questa proprietà decide quale file ambiente Blazor deve caricare.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"