Esercitazioni > Solution Designer > Workflow > Personalizzazione di workflow in Therefore⢠Online Sviluppo di una nuova funzione Azure |
Scroll |
Come si sviluppa una nuova funzione Azure?
BenchÊ siano disponibili molti linguaggi e strumenti, in questa sede viene descritta soltanto la procedura per creare funzioni Azure utilizzando C# in Visual Studio. Questo è l'approccio consigliato perchÊ consente di eseguire il testing e il debugging delle funzioni Azure localmente, senza che sia necessario un abbonamento Azure, semplicemente premendo F5. à anche possibile implementare la funzione in Azure da Visual Studio.
Assicurarsi che Visual Studio sia installato con il carico di lavoro "Sviluppo di Azure".
In Visual Studio, fare clic sul menu "Tools" e poi su "Get Tools and FeaturesâŚ" per aprire il programma di installazione.
Aprire la scheda "Carichi di lavoro" e cercare "Sviluppo di Azure". Attivare la casella di controllo in alto a destra e fare clic su "Modifica" per applicare le modifiche.
|
---|
Come si crea una nuova funzione Azure?
Se il carico di lavoro "Sviluppo di Azure" è stato installato correttamente, "Funzioni di Azure" sarà una delle opzioni disponibili quando si crea un nuovo progetto in Visual Studio. Si consiglia comunque di iniziare con l'esempio "EmptyFunctionApp" anzichÊ cominciare una procedura di sviluppo da zero. Selezionare "Azure Functions v2" ("v1" non sarà piÚ disponibile in futuro) e "Http trigger", poichÊ Therefore⢠attualmente supporta solo la chiamata di funzioni Azure tramite richieste HTTP(S). I diritti di accesso saranno applicati solo quando la funzione sarà pubblicata, non per lo sviluppo locale. Se si sceglie "Anonymous", non è richiesta alcuna chiave. "Function" invece richiede la chiave funzione, mentre "Admin" richiede la chiave Function App. Si può usare anche la chiave master. |
---|
Come si usa REST per le chiamate API web?
Aggiungere al progetto la cartella "Therefore" dall'esempio âEmptyFunctionAppâ.
I file non sono indispensabili, ma rendono lo sviluppo piÚ semplice e piÚ veloce. Se è necessario aggiornare WebAPIContract.cs a seguito di modifiche apportate all'API web, procedere nel modo seguente:
1.Scaricare e installare Silverlight 5.0 o successivo 2.Verificare che il servizio XML Manager sia in esecuzione 3.Creare una cartella temporanea, ad es. âC:\WebAPIâ 4.Aprire una riga di comando e andare a âC:\Program Files (x86)\Microsoft SDKs\Silverlight\v5.0\Toolsâ 5.Eseguire il comando seguente: SlSvcUtil.exe http://servername:8000/theservice/v0001?singlewsdl /out: C:\WebAPI \WebAPIContract.cs /config: C:\WebAPI \output /namespace:http://schemas.therefore.net/webservices/interop/v0001/types,Therefore.WebAPI 6. Andare a âC:\WebAPIâ e copiare il file WebAPIContract.cs nella cartella âTherefore\WebAPIâ, sostituendo quello precedente.
Come descritto nel progetto di esempio, la funzione SendRequest della classe Therefore.WebAPI.WebApiClient può essere utilizzata per inviare una richiesta. Per un elenco di tutti i comandi disponibili, consultare la documentazione dell'API web.
Per utilizzare WebAPIContract.cs è necessario il pacchetto NuGet âSystem.ServiceModel.Httpâ. Per maggiori informazioni, vedere la sezione Dipendenze.
|
---|
Come si usa SOAP per le chiamate API web?
Aggiungere al progetto la cartella âThereforeâ dal campione âEmptyFunctionAppâ, quindi eliminare la sottocartella âWebAPIâ, che è richiesta solo per le chiamate REST. PoichĂŠ l'uso di SOAP non è consigliato, il valore di âUseRESTâ nella classe TheConfig deve essere impostato su âfalseâ, altrimenti la proprietĂ WebAPIBaseUrl risulterĂ errata.
Aggiungere Therefore⢠Web API come servizio connesso:
Come servizio connesso, scegliere âMicrosoft WCF Web Service Reference Providerâ. Inserire l'URL presso il quale è in esecuzione il servizio web e premere âGoâ. Se il servizio è in esecuzione, dovrebbe essere indicato in Servizi come âThereforeServiceâ.
Specificare uno spazio dei nomi come âTherefore.WebAPIâ e premere Fine.
La classe Therefore.WebAPI.ThereforeServiceClient può essere utilizzata per effettuare chiamate API web.
Per maggiori informazioni sul pacchetto NuGet âSystem.ServiceModel.Httpâ, vedere la sezione Dipendenze.
|
---|
Per aggiungere il pacchetto NuGet âSystem.ServiceModel.Httpâ, fare clic con il pulsante destro sul progetto e selezionare âGestisci pacchetti NuGetâŚâ
Passare alla scheda Sfoglia e cercare âSystem.ServiceModel.Httpâ, selezionarlo e fare clic su Installa.
Se viene visualizzato l'errore seguente durante la compilazione, è presente un bug non ancora risolto:
System.IO.FileNotFoundException: Could not load file or assembly 'System.Private.ServiceModel, Version=4.5.0.3, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a'. Il sistema non è in grado di trovare il file specificato.
Per risolvere questo problema, modificare il file .csproj e aggiungere la seguente configurazione come elemento secondario del nodo radice âProgettoâ:
<!--this is temporary workaround for issue https://github.com/dotnet/wcf/issues/2824 also requires nuget package--> <Target Name="CopySPSM" BeforeTargets="Build"> <Copy SourceFiles="$(USERPROFILE)\.nuget\packages\system.private.servicemodel\ </Target> <ItemGroup> <None Include="$(USERPROFILE)\.nuget\packages\system.private.servicemodel\ </ItemGroup> <!--end workaround-->
Nota: potrebbe essere necessario modificare i numeri di versione e le interruzioni di riga. |
---|
EmptyFunctionApp è una breve funzione Azure di esempio che recupera i parametri passati da Thereforeâ˘, configura l'oggetto WebApiClient per prepararlo per l'uso e alla fine restituisce i parametri richiesti. Ă stata creata per essere utilizzata come punto di partenza per ulteriori attivitĂ di sviluppo.
public static async Task<IActionResult> Run( [HttpTrigger(AuthorizationLevel.Function, "post", Route = "v1/Function1")] HttpRequest req, ILogger log) { string errMsg = ""; // error message that will be returned to Therefore for logging Exception logEx = null; // exception details for logging bool routingDone = false; // false: WF instance will be routed to the next task. // true: WF instance will not be routed because routing was done by WebAPI call tryâŚ
if (!String.IsNullOrWhiteSpace(errMsg) || logEx != null) log.LogError(logEx, errMsg); // log the error so it can be viewed in the Azure Portal
TheRespParams respParams = new TheRespParams(errMsg, routingDone); return new OkObjectResult(respParams.Serialize());
La funzione âRunâ è la funzione principale che verrĂ chiamata in una richiesta HTTP.
Il livello di autorizzazione âAuthorizationLevelâ definito in HttpTrigger sarĂ applicato solo quando la funzione sarĂ pubblicata, non per lo sviluppo locale. Se si sceglie "Anonymous" non è richiesta alcuna chiave, mentre "Function" richiede la chiave funzione e "Admin" richiede la chiave Function App. Si può usare anche la chiave master.
Il valore âRouteâ definito in HttpTrigger come âv1/Function1â fornisce un modo per gestire le versioni. Supponiamo che una nuova versione del workflow richieda una nuova versione della funzione, ma che alcune istanze debbano rimanere sulla versione vecchia. Per soddisfare questa esigenza, si potrebbe copiare la funzione e cambiare il valore Route da âv1â a âv2â nel codice e nella configurazione lavoro dei nuovi lavori. Una App per le funzioni può includere piĂš funzioni.
Alla fine del codice campione, gli errori eventualmente verificatisi vengono registrati in Azure e anche passati a un oggetto delle classe âTheRespParamsâ. La classe âTheRespParamsâ provvede a convertire i parametri di restituzione obbligatori per Therefore⢠come oggetto JSON.
Viene sempre creato un oggetto âOkObjectResultâ, che restituisce l'oggetto JSON con codice di stato HTTP 200. Eventuali messaggi di errore in âerrMsgâ saranno restituiti a Therefore⢠per essere registrati nello storico workflow. Sul lato di Thereforeâ˘, la chiamata alla funzione ha avuto esito positivo se non si è verificato nessun errore (errMsg è vuoto). |
---|
try { // read parameters passed by Therefore string requestBody = await new StreamReader(req.Body).ReadToEndAsync(); TheReqParams reqParams = TheReqParams.Deserialize(requestBody); if (reqParams == null || reqParams.TheInstanceNo <= 0 || reqParams.TheTokenNo <= 0) return new BadRequestObjectResult("Mandatory parameter not set"); // function was not called by Therefore, return HTTP error 400 reqParams.LogWFInfo(log); // log workflow information now, in case the function crashes later
// configuration TheConfig config = TheConfig.Instance; config.WebAPIBaseUrl = reqParams.TheWebAPIUrl; config.Tenant = reqParams.TheTenant;
// Web API connection WebApiClient client; // specify a user name and password if the permissions from the accessToken would not be sufficient if (!String.IsNullOrEmpty(config.UserName) && !String.IsNullOrEmpty(config.Password)) // connection with user name and password/token: client = new WebApiClient(config.UserName, config.Password, config.IsToken, config.WebAPIUrl, config.Tenant, config.Language, config.RequestTimeout, config.ClientTimezoneIANA); else // connection with JWT Bearer token 'accessToken' client = new WebApiClient(reqParams.TheAccessToken, config.WebAPIUrl, config.Tenant, config.Language, config.RequestTimeout, config.ClientTimezoneIANA);
// Add your code here }
âTheReqParamsâ provvede alla lettura di tutti i parametri passati da Thereforeâ˘.
La classe âTheConfigâ ha varie proprietĂ , necessarie per la chiamata WebAPI, come language, timezone, request timeout e molte altre. Possono essere configurate in base alle proprie esigenze oppure lasciate con i valori predefiniti.
Due proprietĂ non hanno valori predefiniti utili: âWebAPIBaseUrlâ e âTenantâ. I valori vengono passati dal server Therefore⢠e devono semplicemente essere assegnati all'oggetto âTheConfigâ.
La classe TheConfig leggerĂ le proprietĂ UserName e Password da âlocal.settings.jsonâ durante lo sviluppo locale, oppure dalle Impostazioni applicazioni quando si opera in Azure.
Le istanze dell'oggetto WebApiClient possono essere create utilizzando le credenziali utente o il token JWT,
a seconda che le credenziali risultino vuote o meno. Se le credenziali non sono configurate, viene utilizzato il token JWT.
La funzione SendRequest della classe WebApiClient può essere utilizzata per inviare una richiesta. Per un elenco di tutti i comandi disponibili, consultare la documentazione dell'API web.
Nel commento âaggiungere qui il proprio codiceâ, è possibile eseguire richieste Web API e implementare la propria logica aziendale.
A seconda che l'esito sia negativo o positivo, viene restituito un errore oppure una stringa vuota, per segnalare a Therefore⢠se la funzione viene eseguita nel modo previsto (al di fuori dell'istruzione try/catch). |
---|
Questa documentazione spiega solo come inviare richieste mediante l'API Web in generale, ma non descrive le singole funzioni disponibili. Per informazioni dettagliate sull'uso delle varie funzioni, consultare la documentazione dell'API web.
Le funzioni API web possono essere chiamate utilizzando la classe Therefore.WebAPI.WebApiClient.
Per creare una nuova istanza sono necessari alcuni parametri. Come descritto nell'esempio EmptyFunctionApp, l'oggetto âTheConfigâ può essere di aiuto a questo scopo. Quando si configura un requestTimeout, tenere presente che la funzione stessa viene chiamata tramite una richiesta HTTP e scade dopo un massimo di 230 secondi. Credenziali: WebApiClient client = new WebApiClient(username, password, isToken, webApiUrl, tenant, language, requestTimeout, clientTimezoneIANA);
Token bearer JWT: WebApiClient client = new WebApiClient(bearerToken, webApiUrl, tenant, language, requestTimeout, clientTimezoneIANA);
Dopo aver creato l'oggetto WebApiClient, è possibile inviare le richieste utilizzando la funzione âSendRequestâ. Task<TResp> SendRequest<TReq, TResp>(TReq requestData)
Questa singola funzione può essere utilizzata per tutte le funzionalitĂ dell'API web. La richiesta viene determinata dal tipo di parametro di richiesta e di risposta. Richiesta e riposta devono coincidere. Ad esempio, è possibile utilizzare GetDocumentParams con GetDocumentResponse per eseguire una richiesta âGetDocumentâ.
Esempio: Recupero di un documento (con file):
Ă possibile recuperare documenti utilizzando GetDocumentParams con GetDocumentResponse nella funzione SendRequest.
Nel parametro di richiesta GetDocumentParams, è necessario specificare il numero documento.
Se devono essere recuperati anche i file del documento, occorre impostare la proprietĂ âIsStreamsInfoAndDataNeededâ su true. Se sono necessarie solo le informazioni di flusso (stream) senza i dati veri e propri, âIsStreamsInfoNeededâ può essere impostata su false.
// Create request parameters GetDocumentParams parameters = new GetDocumentParams(); parameters.DocNo = docNo; parameters.IsStreamsInfoAndDataNeeded = true; parameters.IsIndexDataValuesNeeded = true;
// Send the request GetDocumentResponse docResp = client.SendRequest<GetDocumentParams, GetDocumentResponse>(parameters).Result;
// Extract all file streams to the specified directory string extractDir = Path.GetTempPath(); foreach (var streamInfo in docResp.StreamsInfo) { string extractFileName = Path.Combine(extractDir, streamInfo.FileName); File.WriteAllBytes(extractFileName, streamInfo.StreamData); } |
---|
Risposta: Ogni funzione chiamata dal nuovo lavoro di workflow âChiama funzioneâ deve restituire alcuni parametri di risposta obbligatori in formato JSON all'interno della risposta HTTP: •TheError: valore stringa obbligatorio. Se la stringa non è vuota, verrĂ visualizzata nello storico del workflow. L'istanza sarĂ contrassegnata come errata, a meno che non venga richiesto un nuovo tentativo in un momento seguente. •TheRetryAfterMin: valore intero opzionale. Può essere utilizzato per richiedere l'esecuzione di un nuovo tentativo da parte di Thereforeâ˘. Se è impostato su un valore maggiore di zero, il lavoro 'Chiama funzione' funzionerĂ come un lavoro 'Attendi' e invierĂ una nuova richiesta una volta trascorso (circa) il numero di minuti specificato. Se "TheError" è impostato, viene trattato come avviso e non contrassegna l'istanza di workflow come errata. •TheRoutingDone: valore booleano obbligatorio, a meno che non sia stato specificato un valore per "TheRetryAfterMin". Può essere impostato su 'true' per indicare che l'indirizzamento dell'istanza di workflow è giĂ stato eseguito dalla funzione mediante la funzione Web API 'FinishCurrentWorkflowTask'. Se è impostato su 'false', Therefore⢠indirizza l'istanza di workflow al lavoro successivo. Se "TheRetryAfterMin" è specificato, l'indirizzamento non viene eseguito per il momento, quindi in questo caso è facoltativo. Per semplificare la restituzione di questi parametri, è stata creata la classe âTheRespParamsâ, che li include tutti sotto forma di proprietĂ e fornisce la funzione âSerializeâ per la creazione di un oggetto JSON. Sono disponibili due modi per inizializzare un oggetto di questa classe: •Utilizzando âTheErrorâ e âTheRoutingDoneâ TheRespParams respParams = new TheRespParams(errMsg, routingDone); Deve essere utilizzato come modalitĂ predefinita oppure in caso di errore. Entrambi i valori sono opzionali.
Per impostazione predefinita, errMsg è vuoto e routingDone è false.
•Utilizzando âTheErrorâ e âTheRetryAfterMinâ TheRespParams respParams = new TheRespParams(retryAfterMin, errMsg);
Indica a Therefore⢠di effettuare un nuovo tentativo di chiamata della funzione in un secondo momento. Il valore intero retryAfterMin deve essere maggiore di zero; errMsg è facoltativo e e viene registrato come informazione aggiuntiva (non come errore) nello storico del workflow.
Richiesta:
Con ogni chiamata di funzione, Therefore⢠invia i parametri seguenti alla funzione come oggetto JSON insieme alla richiesta HTTPS:
•TheWebAPIUrl: contiene l'impostazione server "URL API web", che può essere configurata nella scheda 'XML Web Service' o nelle impostazioni avanzate. Questa impostazione sarĂ giĂ configurata nel modo corretto se si usa Therefore⢠Online, ma può essere lasciata vuota se non è configurata. •TheTenant: nome del locatario (tenant) corrente. SarĂ vuoto se il sistema non utilizza la modalitĂ di multi-locazione. •TheAccessToken: un token JWT che può essere utilizzato per connettersi a Therefore⢠mediante l'API web. Il token è configurato in modo da concedere a "$TheWFSystem" l'accesso utente al documento o al fascicolo associato con l'istanza di workflow corrente, nonchĂŠ all'istanza di workflow corrente e al token. Con le autorizzazioni concesse è possibile modificare il documento associato, il fascicolo o i documenti associati al fascicolo. Inoltre, le autorizzazioni devono consentire l'indirizzamento dell'istanza di workflow a un altro lavoro. Non è invece possibile creare nuovi documenti o fascicoli, nĂŠ eseguire query. Non si può nemmeno eliminare il fascicolo o documento principale associato all'istanza di workflow, nĂŠ l'istanza di workflow stessa. •TheInstanceNo: numero istanza dell'istanza di workflow corrente. •TheTokenNo: numero di token dell'istanza di workflow corrente. •TheCaseNo: numero di fascicolo del fascicolo associato all'istanza di workflow corrente. Questo parametro è disponibile solo per i workflow basati su fascicoli. •TheDocNo: numero di documento del documento principale associato all'istanza di workflow corrente. Questo parametro è disponibile solo per i workflow basati su documenti. •TheSettings: oggetto JSON (passato come stringa) di tutte le 'Impostazioni funzione personalizzate' configurate.
Può essere vuoto oppure utilizzato per passare ulteriori parametri alla funzione chiamata. Nome e valore di ogni impostazione possono essere scelti liberamente, ma i nomi devono essere univoci in base alla specifica JSON. Tutti i valori vengono trattati come valori stringa e devono essere convertiti in un altro tipo all'interno della funzione, se richiesto.
Per semplificare la lettura di questi parametri dalla stringa JSON, è stata creata la classe âTheReqParamsâ, che li include tutti sotto forma di proprietĂ e fornisce la funzione âDeserializeâ per la creazione di un oggetto da una stringa JSON.
Il codice della classe âTheReqParamsâ può essere utilizzato come esempio per le impostazioni personalizzate dell'utente.
Supponiamo che sia stato configurato âTaskNoâ con un valore intero nella configurazione del lavoro di workflow come âimpostazione di funzione personalizzataâ; per leggerlo, si potrebbe usare la classe seguente:
class CustomSettings { public string taskNo; public int TaskNo { get { return int.Parse(taskNo); } set { taskNo = value.ToString(); } } public static CustomSettings Deserialize(string json) { return JsonConvert.DeserializeObject<CustomSettings>(json); } }
|
---|
Come si specificano nome utente e password per la connessione API web?
Il codice nel progetto di esempio controllerĂ principalmente se sono stati specificati nome utente e password, e il token di accesso fornito sarĂ utilizzato solo come alternativa. In tal modo, il token verrĂ utilizzato come modalitĂ predefinita solo se non vengono specificati nome utente e password. Queste credenziali possono essere necessarie qualora fossero richieste piĂš autorizzazioni di quelle fornite dal token.
TheConfig.cs usa ConfigurationBuilder per accedere sia a âlocal.settings.jsonâ che alle variabili di ambiente per cercare la configurazione.
Quando si esegue la funzione Azure localmente, l'impostazione viene letta da âlocal.settings.jsonâ. Per aggiungere l'impostazione, inserire le impostazioni âWebAPIUserâ e âWebAPIPasswordâ nell'oggetto JSON âValuesâ come nell'esempio seguente: { "IsEncrypted": false, "Values": { "AzureWebJobsStorage": "UseDevelopmentStorage=true", "FUNCTIONS_WORKER_RUNTIME": "dotnet", "WebAPIUser": "Domain\\TestUser1", "WebAPIPassword": "secretpassword" } }
Quando la funzione è stata distribuita in Azure, le impostazioni vengono lette dalle Impostazioni delle app.
Per aggiungere nome utente e password, selezionare l'App per le funzioni nel portale Azure e fare clic su âImpostazioni applicazioneâ. Le nuove impostazioni possono essere aggiunte facendo clic su âNuova impostazione applicazioneâ. Al termine fare clic su âSalvaâ.
|
---|
Le funzioni Azure possono essere distribuite alla piattaforma Microsoft Azure direttamente da Visual Studio. Fare clic con il pulsante destro sul progetto, quindi selezionare âPubblicaâ per pubblicare la funzione Azure. Selezionare âApp per le funzioni di Azureâ per caricarla su Azure direttamente da Visual Studio.
Se si sceglie âCartellaâ, la funzione va caricata manualmente tramite FTP. Il nome dell'App per le funzioni deve essere unico a livello globale, poichĂŠ viene creato un piccolo sito web (âhttps://NomeAppFunzioni.azurewebsites.netâ) pubblicamente disponibile, indipendentemente dal fatto che la funzione sia attivabile via HTTP o meno (il trigger HTTP utilizzerĂ lo stesso URL). Ogni App per le funzioni di Azure deve avere un Account di archiviazione in cui salvare le funzioni e i registri. Sono disponibili due diverse opzioni per l'hosting di un'App per le funzioni:
•Piano a consumo
•Piano di servizio app
Una volta implementata la funzione, la chiamata genererĂ un errore 401 âNon autorizzatoâ.
Per impostazione predefinita, una chiave funzione viene utilizzata per l'autorizzazione da fornire per ogni chiamata.
La chiave può essere ottenuta dal portale Azure. Dopo aver aperto l'App per le funzioni, fare clic su âGestioneâ e copiare la chiave âpredefinitaâ.
Una chiamata da Therefore⢠includerĂ la chiave come âx-functions-keyâ nell'intestazione di richiesta, se configurata.
Per testare l'esito positivo dell'implementazione senza Therefore⢠è possibile usare cURL o un programma analogo.
curl âd ââ âi âX POST âhttps://AppName.azurwebsites.net/api/Route?code=FunctionKeyâ
Se non viene definito un percorso (route), viene invece utilizzato il nome funzione.
|
---|
Š 2024 Therefore Corporation. Tutti i diritti riservati.