Esempio, il progetto Microsoft.EntityFrameworkCore.Relation di EntityFramework ha il seguente testo nei file di risorse:
...
<data name="FromSqlMissingColumn" xml:space="preserve">
<value>The required column '{column}' was not present in the results of a 'FromSql' operation.</value>
</data>
...
che genera il seguente codice C #:
...
/// <summary>
/// The required column '{column}' was not present in the results of a 'FromSql' operation.
/// </summary>
public static string FromSqlMissingColumn([CanBeNull] object column)
{
return string.Format(CultureInfo.CurrentCulture, GetString("FromSqlMissingColumn", "column"), column);
}
...
private static string GetString(string name, params string[] formatterNames)
{
var value = _resourceManager.GetString(name);
Debug.Assert(value != null);
if (formatterNames != null)
{
for (var i = 0; i < formatterNames.Length; i++)
{
value = value.Replace("{" + formatterNames[i] + "}", "{" + i + "}");
}
}
return value;
}
...
Ma quando modifico il file in VS e lo salvo, ottengo solo semplici proprietà generate, come:
...
/// <summary>
/// The required column '{column}' was not present in the results of a 'FromSql' operation.
/// </summary>
public static string FromSqlMissingColumn
{
get { return ResourceManager.GetString("FromSqlMissingColumn"); }
}
...
I file in questione possono essere trovati qui:
Quindi la domanda di nuovo - Come hanno fatto, e come ho potuto ottenere lo stesso risultato?
Come hanno fatto?
In primo luogo dovrebbe essere ovvio che non usano il ResXFileCodeGenerator
standard, ma alcuni tool di generazione del codice personalizzato.
Attualmente ci sono 2 modi standard per generare codice: la vecchia scuola che usa uno Custom Tool
simile a ResXFileCodeGenerator
, o il modo moderno usando un modello T4 . Quindi vediamo.
La voce corrispondente all'interno del file Microsoft.EntityFrameworkCore.Relational.csproj ha il seguente aspetto:
<ItemGroup>
<EmbeddedResource Include="Properties\RelationalStrings.resx">
<LogicalName>Microsoft.EntityFrameworkCore.Relational.Properties.RelationalStrings.resources</LogicalName>
</EmbeddedResource>
</ItemGroup>
Come possiamo vedere, sicuramente non usano lo Custom Tool
.
Quindi dovrebbe essere un modello T4. E infatti subito dopo l'elemento sopra possiamo vedere:
<ItemGroup>
<Content Include="..\..\tools\Resources.tt">
<Link>Properties\Resources.tt</Link>
<Generator>TextTemplatingFileGenerator</Generator>
<LastGenOutput>Resources.cs</LastGenOutput>
<CustomToolNamespace>Microsoft.EntityFrameworkCore.Internal</CustomToolNamespace>
</Content>
<Content Include="Properties\Microsoft.EntityFrameworkCore.Relational.rd.xml" />
</ItemGroup>
Quindi eccoti!
Ora, non so quale sia lo scopo del file xml
incluso senza immergersi nell'implementazione (potrebbe essere qualcosa che viene utilizzato dal generatore, come le opzioni o qualcosa del genere), ma la generazione del codice reale è contenuta nelle seguenti risorse. tt file.
come posso ottenere lo stesso risultato?
Immagino che tu stia chiedendo i tuoi progetti. Bene, puoi fare qualcosa di simile. Seleziona il tuo file resx , vai su Properties
e deseleziona lo Custom Tool
. Quindi aggiungi il T4 template
al tuo progetto e scrivi la generazione del codice (non sono sicuro che la licenza ti permetta di usare il loro codice, quindi se vuoi farlo, assicurati di controllare prima se è permesso). Ma il principio sarebbe lo stesso.
Credo che il team EF utilizzi il proprio Custom Tool
personalizzato Custom Tool
a tale scopo. Ma visual studio utilizza PublicResXFileCodeGenerator
come strumento personalizzato predefinito per i file .resx
e questo strumento non ha tale funzionalità come PublicResXFileCodeGenerator
e la sua classe base ResXFileCodeGenerator
(entrambi possono essere trovati nell'assembly Microsoft.VisualStudio.Design
) è solo un wrapper per lo studio visivo attorno a StronglyTypedResourceBuilder
.
Implementano IVsSingleFileGenerator
(che si trova nell'assembly Microsoft.VisualStudio.Shell.Interop
). Quindi questo è il posto dove puoi iniziare ad implementare il tuo Custom Tool
. Avviare una nuova Class Library
, aggiungere Microsoft.VisualStudio.Shell.Interop
riferimenti a Microsoft.VisualStudio.Shell.14.0
e Microsoft.VisualStudio.Shell.Interop
. Crea una nuova classe e implementa questa interfaccia. Interface IVsSingleFileGenerator
è piuttosto semplice. Contiene solo due metodi:
DefaultExtension
che restituisce l'estensione per il file generato (con un periodo iniziale) come out string pbstrDefaultExtension
paratemer e VSConstant.S_OK
come valore di ritorno (ovviamente se tutto è OK).
Generate
quale è accetta:
wszInputFilePath
- percorso del file di input, potrebbe essere nullo, non usarlo. bstrInputFileContents
: deve essere utilizzato il contenuto del file di input. wszDefaultNamespace
- spazio dei nomi predefinito (non è possibile sapere subito perché ResXFileCodeGenerator
interpone con Visual Studio per ottenere lo spazio dei nomi invece di utilizzare questo parametro). rgbOutputFileContents
- array di byte del file generato. È necessario includere i byte di firma UNICODE o UTF-8 nell'array di byte restituito, poiché si tratta di un flusso non elaborato. La memoria per rgbOutputFileContents deve essere allocata utilizzando la chiamata .NET Framework, Marshal.AllocCoTaskMem
o la chiamata di sistema Win32 equivalente, CoTaskMemAlloc
. Il sistema di progetto è responsabile della liberazione di questa memoria. pcbOutput
: conteggio dei byte nell'array rgbOutputFileContent
. pGenerateProgress
- Un riferimento all'interfaccia IVsGeneratorProgress
attraverso il quale il generatore può segnalare il suo avanzamento al sistema del progetto. E restituisce VSConstant.S_OK
se tutto va bene o il codice di errore corrispondente.
Inoltre c'è una piccola guida sull'implementazione . Ma questa guida non dice troppo. La cosa più utile è come registrare il proprio generatore.
Faresti meglio ad immergerti nel codice ResXFileCodeGenerator
(o semplicemente decompilarlo) per l'esempio di implementazione o per ottenere alcuni suggerimenti come il modo di interagire con Visual Studio. Ma non vedo alcun motivo per sottoporre a VS il fatto che tutto ciò di cui hai bisogno ti sia già stato fornito. .resx
contenuto del file potrebbe essere letto da ResXResourceReader.FromFileContents
.
Resto di tutto sarebbe semplice come hai i nomi di risorse e valori e solo bisogno di restituire array di byte del file generato. Penso che l'analisi dei valori delle risorse per avere un errore in fase di compilazione di formato non valido (ad esempio {{param}}}
) sarebbe la più grande difficoltà.
Quando i valori sono analizzati e i parametri per il tuo metodo imminente trovati puoi generare codice (di nuovo come esempio puoi fare riferimento a ResXFileCodeGenerator
e StronglyTypedResourceBuilder
, oppure farlo da te come vuoi, via CodeDom o comporre manualmente il codice sorgente). Inoltre, non dovrebbe essere difficile in quanto hai già un esempio dei metodi che devi generare nella domanda che hai postato.
Compila il tuo generatore, registralo, impostalo nella proprietà Custom Tool
dei tuoi file .resx
e otterrai classi di risorse con metodi invece di proprietà.
Inoltre puoi condividerlo su Github con altri. :)
Ecco le istruzioni dalla registrazione degli strumenti personalizzati (dato che msdn link potrebbe morire presto o tardi):
Per rendere disponibile uno strumento personalizzato in Visual Studio, è necessario registrarlo in modo che Visual Studio possa istanziarlo e associarlo a un particolare tipo di progetto.
Registrare la DLL dello strumento personalizzato nel registro locale di Visual Studio o nel registro di sistema, in HKEY_CLASSES_ROOT
.
Ad esempio, ecco le informazioni di registrazione per lo strumento personalizzato MSDataSetGenerator
gestito, fornito con Visual Studio:
[HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\VisualStudio\14.0\CLSID\{E76D53CC-3D4F-40A2-BD4D-4F3419755476}]
@="COM+ class: Microsoft.VSDesigner.CodeGenerator.TypedDataSourceGenerator.DataSourceGeneratorWrapper"
"InprocServer32"="C:\\WINDOWS\\system32\\mscoree.dll"
"ThreadingModel"="Both"
"Class"="Microsoft.VSDesigner.CodeGenerator.TypedDataSourceGenerator.DataSourceGeneratorWrapper"
"Assembly"="Microsoft.VSDesigner, Version=14.0.0.0, Culture=Neutral, PublicKeyToken=b03f5f7f11d50a3a"
Creare una chiave di registro nell'hive di Visual Studio desiderato in Generators\GUID
dove GUID
è il GUID definito dal sistema o servizio di progetto della lingua specifica. Il nome della chiave diventa il nome programmatico del tuo strumento personalizzato. La chiave dello strumento personalizzato ha i seguenti valori:
(Default)
- Opzionale. Fornisce una descrizione user-friendly dello strumento personalizzato. Questo parametro è facoltativo, ma consigliato.
CLSID
- Richiesto. Specifica l'identificatore della libreria di classi del componente COM che implementa IVsSingleFileGenerator.
GeneratesDesignTimeSource
- Obbligatorio. Indica se i tipi di file prodotti da questo strumento personalizzato sono resi disponibili ai visual designer. Il valore di questo parametro deve essere (zero) 0 per i tipi non disponibili per i visual designer o (uno) 1 per i tipi disponibili per i visual designer.
Nota, è necessario registrare lo strumento personalizzato separatamente per ogni lingua per cui si desidera che lo strumento personalizzato sia disponibile.
Ad esempio, MSDataSetGenerator
registra una sola volta per ogni lingua:
[HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\VisualStudio\14.0\Generators\{164b10b9-b200-11d0-8c61-00a0c91e29d5}\MSDataSetGenerator]
@="Microsoft VB Code Generator for XSD"
"CLSID"="{E76D53CC-3D4F-40a2-BD4D-4F3419755476}"
"GeneratesDesignTimeSource"=dword:00000001
[HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\VisualStudio\14.0\Generators\{fae04ec1-301f-11d3-bf4b-00c04f79efbc}\MSDataSetGenerator]
@="Microsoft C# Code Generator for XSD"
"CLSID"="{E76D53CC-3D4F-40a2-BD4D-4F3419755476}"
"GeneratesDesignTimeSource"=dword:00000001
[HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\VisualStudio\14.0\Generators\{e6fdf8b0-f3d1-11d4-8576-0002a516ece8}\MSDataSetGenerator]
@="Microsoft J# Code Generator for XSD"
"CLSID"="{E76D53CC-3D4F-40a2-BD4D-4F3419755476}"
"GeneratesDesignTimeSource"=dword:00000001