Background processing with .NET

Many apps need to do background processing outside of the context of a web request. This tutorial creates a web app that lets users input text to translate, and then displays a list of previous translations. The translation is done in a background process to avoid blocking the user's request.

The following diagram illustrates the translation request process.

Diagram of architecture.

Here is the sequence of events for how the tutorial app works:

  1. Visit the web page to see a list of previous translations, stored in Firestore.
  2. Request a translation of text by entering an HTML form.
  3. The translation request is published to Pub/Sub.
  4. A Cloud Run service subscribed to that Pub/Sub topic is triggered.
  5. The Cloud Run service uses Cloud Translation to translate the text.
  6. The Cloud Run service stores the result in Firestore.

This tutorial is intended for anyone who is interested in learning aboutbackground processing with Google Cloud. No prior experience isrequired with Pub/Sub, Firestore, App Engine, orCloud Run functions. However, to understand all of the code, some experience with.NET, JavaScript, and HTML is helpful.

Objectives

  • Understand and deploy Cloud Run services.
  • Try the app.

Costs

In this document, you use the following billable components of Google Cloud:

To generate a cost estimate based on your projected usage, use thepricing calculator.

New Google Cloud users might be eligible for afree trial.

When you finish the tasks that are described in this document, you can avoid continued billing by deleting the resources that you created. For more information, seeClean up.

Before you begin

  1. Sign in to your Google Cloud account. If you're new to Google Cloud, create an account to evaluate how our products perform in real-world scenarios. New customers also get $300 in free credits to run, test, and deploy workloads.
  2. In the Google Cloud console, on the project selector page, select or create a Google Cloud project.

    Roles required to select or create a project

    • Select a project: Selecting a project doesn't require a specific IAM role—you can select any project that you've been granted a role on.
    • Create a project: To create a project, you need the Project Creator role (roles/resourcemanager.projectCreator), which contains theresourcemanager.projects.create permission.Learn how to grant roles.
    Note: If you don't plan to keep the resources that you create in this procedure, create a project instead of selecting an existing project. After you finish these steps, you can delete the project, removing all resources associated with the project.

    Go to project selector

  3. Verify that billing is enabled for your Google Cloud project.

  4. Enable the Firestore, Cloud Run, Pub/Sub, and Cloud Translation APIs.

    Roles required to enable APIs

    To enable APIs, you need the Service Usage Admin IAM role (roles/serviceusage.serviceUsageAdmin), which contains theserviceusage.services.enable permission.Learn how to grant roles.

    Enable the APIs

  5. Install the Google Cloud CLI.

  6. If you're using an external identity provider (IdP), you must first sign in to the gcloud CLI with your federated identity.

  7. Toinitialize the gcloud CLI, run the following command:

    gcloudinit
  8. In the Google Cloud console, on the project selector page, select or create a Google Cloud project.

    Roles required to select or create a project

    • Select a project: Selecting a project doesn't require a specific IAM role—you can select any project that you've been granted a role on.
    • Create a project: To create a project, you need the Project Creator role (roles/resourcemanager.projectCreator), which contains theresourcemanager.projects.create permission.Learn how to grant roles.
    Note: If you don't plan to keep the resources that you create in this procedure, create a project instead of selecting an existing project. After you finish these steps, you can delete the project, removing all resources associated with the project.

    Go to project selector

  9. Verify that billing is enabled for your Google Cloud project.

  10. Enable the Firestore, Cloud Run, Pub/Sub, and Cloud Translation APIs.

    Roles required to enable APIs

    To enable APIs, you need the Service Usage Admin IAM role (roles/serviceusage.serviceUsageAdmin), which contains theserviceusage.services.enable permission.Learn how to grant roles.

    Enable the APIs

  11. Install the Google Cloud CLI.

  12. If you're using an external identity provider (IdP), you must first sign in to the gcloud CLI with your federated identity.

  13. Toinitialize the gcloud CLI, run the following command:

    gcloudinit
  14. Updategcloud components:
    gcloudcomponentsupdate
  15. Prepare your development environment.

    Setting up a .NET development environment

Preparing the app

  1. In your terminal window, clone the sample app repository to your local machine:

    gitclonehttps://github.com/GoogleCloudPlatform/getting-started-dotnet.git

    Alternatively, you can download the sample as a zip file and extract it.

  2. Change to the directory that contains the background task sample code:

    cdgetting-started-dotnet/BackgroundProcessing

Understanding theTranslateWorker service

  • The service starts by importing several dependencies likeFirestore and Translation.

  • The Firestore and Translation clients areinitialized so they can be reused between handler invocations. That way, youdon't have to initialize new clients for every invocation, whichwould slow down execution.

    publicvoidConfigureServices(IServiceCollectionservices){services.AddSingleton<FirestoreDb>(provider=>FirestoreDb.Create(GetFirestoreProjectId()));services.AddSingleton<TranslationClient>(TranslationClient.Create());services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);}
  • The Translation API translates the string to thelanguage you selected.

    varresult=await_translator.TranslateTextAsync(sourceText,"es");
  • The controller's constructor receives the Firestore andPub/Sub clients.

    ThePost method parses the Pub/Submessage to get the text to translate. It uses the message ID as a unique namefor the translation request to make sure itdoesn't store any duplicate translations.

    usingGoogle.Cloud.Firestore;usingGoogle.Cloud.Translation.V2;usingMicrosoft.AspNetCore.Mvc;usingMicrosoft.Extensions.Logging;usingSystem;usingSystem.Collections.Generic;usingSystem.Text;usingSystem.Threading.Tasks;namespaceTranslateWorker.Controllers{/// <summary>/// The message Pubsub posts to our controller./// </summary>publicclassPostMessage{publicPubsubMessagemessage{get;set;}publicstringsubscription{get;set;}}/// <summary>/// Pubsub's inner message./// </summary>publicclassPubsubMessage{publicstringdata{get;set;}publicstringmessageId{get;set;}publicDictionary<string,string>attributes{get;set;}}[Route("api/[controller]")][ApiController]publicclassTranslateController:ControllerBase{privatereadonlyILogger<TranslateController>_logger;privatereadonlyFirestoreDb_firestore;privatereadonlyTranslationClient_translator;// The Firestore collection where we store translations.privatereadonlyCollectionReference_translations;publicTranslateController(ILogger<TranslateController>logger,FirestoreDbfirestore,TranslationClienttranslator){_logger=logger??thrownewArgumentNullException(nameof(logger));_firestore=firestore??thrownewArgumentNullException(nameof(firestore));_translator=translator??thrownewArgumentNullException(nameof(translator));_translations=_firestore.Collection("Translations");}/// <summary>/// Handle a posted message from Pubsub./// </summary>/// <param name="request">The message Pubsub posts to this process.</param>/// <returns>NoContent on success.</returns>[HttpPost]publicasyncTask<IActionResult>Post([FromBody]PostMessagerequest){// Unpack the message from Pubsub.stringsourceText;try{byte[]data=Convert.FromBase64String(request.message.data);sourceText=Encoding.UTF8.GetString(data);}catch(Exceptione){_logger.LogError(1,e,"Bad request");returnBadRequest();}// Translate the source text._logger.LogDebug(2,"Translating {0} to Spanish.",sourceText);varresult=await_translator.TranslateTextAsync(sourceText,"es");// Store the result in Firestore.Translationtranslation=newTranslation(){TimeStamp=DateTime.UtcNow,SourceText=sourceText,TranslatedText=result.TranslatedText};_logger.LogDebug(3,"Saving translation {0} to {1}.",translation.TranslatedText,_translations.Path);await_translations.Document(request.message.messageId).SetAsync(translation);// Return a success code.returnNoContent();}/// <summary>/// Serve a root page so Cloud Run knows this process is healthy./// </summary>[Route("/")]publicIActionResultIndex(){returnContent("Serving translate requests...");}}}

Deploying theTranslateWorker service

  • In theBackgroundProcessing directory, run the PowerShell script to buildand deploy the service to Cloud Run:

    PublishTo-CloudRun.ps1

Understanding thePublishTo-CloudRun.ps1 script

ThePublishTo-CloudRun.ps1 script publishes the service to Cloud Run, andprotects the TranslateWorker service from being abused. If the service permitted allincoming connections, then anyone could post translate requests to the controllerand thereby incur costs. Therefore, you set up the service to only acceptPOST requests from Pub/Sub.

The script does the following:

  1. Builds the app locally usingdotnet publish.
  2. Builds a container that runs the app usingCloud Build.
  3. Deploys the app to Cloud Run.
  4. Enables the project to create Pub/Sub authentication tokens.
  5. Creates a service account to represent the Pub/Sub subscription identity.
  6. Gives the service account permission to invokeTranslateWorker service.
  7. Creates a Pub/Sub topic and subscription.

    # 1. Build the application locally.dotnet publish -c Release# Collect some details about the project that we'll need later.$projectId = gcloud config get-value project$projectNumber = gcloud projects describe $projectId --format="get(projectNumber)"$region = "us-central1"# 2. Use Google Cloud Build to build the worker's container and publish to Google# Container Registry.gcloud builds submit --tag gcr.io/$projectId/translate-worker `    TranslateWorker/bin/Release/netcoreapp2.1/publish# 3. Run the container with Google Cloud Run.gcloud beta run deploy translate-worker --region $region --platform managed `    --image gcr.io/$projectId/translate-worker --no-allow-unauthenticated$url = gcloud beta run services describe translate-worker --platform managed `    --region $region --format="get(status.address.hostname)"# 4. Enable the project to create pubsub authentication tokens.gcloud projects add-iam-policy-binding $projectId `     --member=serviceAccount:service-$projectNumber@gcp-sa-pubsub.iam.gserviceaccount.com `     --role=roles/iam.serviceAccountTokenCreator# 5. Create a service account to represent the Cloud Pub/Sub subscription identity.$serviceAccountExists = gcloud iam service-accounts describe `    cloud-run-pubsub-invoker@$projectId.iam.gserviceaccount.com 2> $nullif (-not $serviceAccountExists) {    gcloud iam service-accounts create cloud-run-pubsub-invoker `        --display-name "Cloud Run Pub/Sub Invoker"}# 6. For Cloud Run, give this service account permission to invoke # translate-worker service.gcloud beta run services add-iam-policy-binding translate-worker `     --member=serviceAccount:cloud-run-pubsub-invoker@$projectId.iam.gserviceaccount.com `     --role=roles/run.invoker --region=$region# 7. Create a pubsub topic and subscription, if they don't already exist.$topicExists = gcloud pubsub topics describe translate-requests 2> $null if (-not $topicExists) {    gcloud pubsub topics create translate-requests}$subscriptionExists = gcloud pubsub subscriptions describe translate-requests 2> $nullif ($subscriptionExists) {    gcloud beta pubsub subscriptions modify-push-config translate-requests `        --push-endpoint $url/api/translate `        --push-auth-service-account cloud-run-pubsub-invoker@$projectId.iam.gserviceaccount.com} else {    gcloud beta pubsub subscriptions create translate-requests `        --topic translate-requests --push-endpoint $url/api/translate `        --push-auth-service-account cloud-run-pubsub-invoker@$projectId.iam.gserviceaccount.com}

Understanding theTranslateUI service

TheTranslateUI service renders a web page that displays recent translations,and accepts requests for new translations.

  • TheStartUp class configures an ASP.NET app and createsPub/Sub and Firestore clients.

    usingGoogle.Apis.Auth.OAuth2;usingGoogle.Cloud.Firestore;usingGoogle.Cloud.PubSub.V1;usingMicrosoft.AspNetCore.Builder;usingMicrosoft.AspNetCore.Hosting;usingMicrosoft.AspNetCore.Mvc;usingMicrosoft.Extensions.Configuration;usingMicrosoft.Extensions.DependencyInjection;usingSystem;usingSystem.Net.Http;namespaceTranslateUI{publicclassStartup{publicStartup(IConfigurationconfiguration){Configuration=configuration;}publicIConfigurationConfiguration{get;}// This method gets called by the runtime. Use this method to add services to the container.publicvoidConfigureServices(IServiceCollectionservices){services.AddSingleton<FirestoreDb>(provider=>FirestoreDb.Create(GetFirestoreProjectId()));services.AddSingleton<PublisherClient>(provider=>PublisherClient.CreateAsync(newTopicName(GetProjectId(),GetTopicName())).Result);services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);}// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.publicvoidConfigure(IApplicationBuilderapp,IHostingEnvironmentenv){if(env.IsDevelopment()){app.UseDeveloperExceptionPage();}else{app.UseExceptionHandler("/Home/Error");// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.app.UseHsts();}app.UseHttpsRedirection();app.UseStaticFiles();app.UseCookiePolicy();app.UseMvc(routes=>{routes.MapRoute(name:"default",template:"{controller=Home}/{action=Index}/{id?}");});}}}
  • The index handlerIndex gets all existing translations fromFirestore and fills aViewModel with the list:

    usingGoogle.Cloud.Firestore;usingGoogle.Cloud.PubSub.V1;usingGoogle.Protobuf;usingMicrosoft.AspNetCore.Mvc;usingSystem.Diagnostics;usingSystem.Linq;usingSystem.Threading.Tasks;usingTranslateUI.Models;namespaceTranslateUI.Controllers{publicclassHomeController:Controller{privatereadonlyFirestoreDb_firestore;privatereadonlyPublisherClient_publisher;privateCollectionReference_translations;publicHomeController(FirestoreDbfirestore,PublisherClientpublisher){_firestore=firestore;_publisher=publisher;_translations=_firestore.Collection("Translations");}[HttpPost][HttpGet]publicasyncTask<IActionResult>Index(stringSourceText){// Look up the most recent 20 translations.varquery=_translations.OrderByDescending("TimeStamp").Limit(20);varsnapshotTask=query.GetSnapshotAsync();if(!string.IsNullOrWhiteSpace(SourceText)){// Submit a new translation request.await_publisher.PublishAsync(newPubsubMessage(){Data=ByteString.CopyFromUtf8(SourceText)});}// Render the page.varmodel=newHomeViewModel(){Translations=(awaitsnapshotTask).Documents.Select(doc=>doc.ConvertTo<Translation>()).ToList(),SourceText=SourceText};returnView(model);}[ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]publicIActionResultError(){returnView(newErrorViewModel{RequestId=Activity.Current?.Id??HttpContext.TraceIdentifier});}}}
  • New translations are requested by submitting an HTML form. The requesttranslation handler validates the request, and publishes amessage to Pub/Sub:

    // Submit a new translation request.await_publisher.PublishAsync(newPubsubMessage(){Data=ByteString.CopyFromUtf8(SourceText)});

Deploying theTranslateUI service

  • In theBackgroundProcessing directory, run the PowerShell script to buildand deploy the service to Cloud Run:

    ./PublishTo-CloudRun.ps1

Understanding thePublishTo-CloudRun.ps1 script

ThePublishTo-CloudRun.ps1 script publishes the app to Cloud Run.

The script does the following:

  1. Builds the app locally usingdotnet publish.
  2. Builds a container that runs the app by usingCloud Build.
  3. Deploys the app to Cloud Run.

    # 1. Build the application locally.dotnet publish -c Release# 2. Use Google Cloud Build to build the UI's container and publish to Google# Container Registry. gcloud builds submit --tag gcr.io/$projectId/translate-ui `    TranslateUI/bin/Release/netcoreapp2.1/publish# 3. Run the container with Google Cloud Run.gcloud beta run deploy translate-ui --region $region --platform managed `    --image gcr.io/$projectId/translate-ui --allow-unauthenticated

Testing the app

After successfully running thePublishTo-CloudRun.ps1 script, tryrequesting a translation.

  1. The final command in thePublishTo-CloudRun.ps1 script tells you the URL for yourUI service. In your terminal window, find the URL for theTranslateUI service:

    gcloudbetarunservicesdescribetranslate-ui--region$region--format="get(status.address.hostname)"
  2. In your browser, go to the URL that you got from the previous step.

    There is a page with an empty list of translations and a form to requestnew translations.

  3. In theText to translate field, enter some text to translate, forexample,Hello, World.

  4. ClickSubmit.

  5. To refresh the page, clickRefresh. There is a newrow in the translation list. If you don't see a translation, wait a few moreseconds and try again. If you still don't see a translation, see thenext section about debugging the app.

Debugging the app

If you cannot connect to your Cloud Run service or don't see newtranslations, check the following:

  • Check that thePublishTo-CloudRun.ps1 script successfully completed and didn'toutput any errors. If there were errors (for example,message=Build failed), fix them, and try running again.

  • Check for errors in the logs:

    1. In the Google Cloud console, go to the Cloud Run page.

      Go to Cloud Run page

    2. Click the service name,translate-ui.

    3. ClickLogs.

Clean up

To avoid incurring charges to your Google Cloud account for the resources used in this tutorial, either delete the project that contains the resources, or keep the project and delete the individual resources.

Delete the Google Cloud project

    Caution: Deleting a project has the following effects:
    • Everything in the project is deleted. If you used an existing project for the tasks in this document, when you delete it, you also delete any other work you've done in the project.
    • Custom project IDs are lost. When you created this project, you might have created a custom project ID that you want to use in the future. To preserve the URLs that use the project ID, such as anappspot.com URL, delete selected resources inside the project instead of deleting the whole project.
  1. In the Google Cloud console, go to theManage resources page.

    Go to Manage resources

  2. In the project list, select the project that you want to delete, and then clickDelete.
  3. In the dialog, type the project ID, and then clickShut down to delete the project.

Delete the Cloud Run services.

  • Delete the Cloud Run services you created in this tutorial:

    gcloudbetarunservicesdelete--region=$regiontranslate-ui
    gcloudbetarunservicesdelete--region=$regiontranslate-worker

What's next

Except as otherwise noted, the content of this page is licensed under theCreative Commons Attribution 4.0 License, and code samples are licensed under theApache 2.0 License. For details, see theGoogle Developers Site Policies. Java is a registered trademark of Oracle and/or its affiliates.

Last updated 2025-12-15 UTC.