- Home>
- .NET core>
- Build and deploy a WebJob alongside web app using azure pipelines
In this post, I’m going to share some of the issues, misunderstandings I ran into when trying to setup and deploy a WebJob alongside a web application using azure pipelines. The WebJob is a console application, and the web app is an ASP.NET core. Both the WebJob and web app target .NET 5.
Microsoft’s documentation on azure WebJob provides good info on how to create and publish a WebJob. However, . In case you are not familiar with WebJobs, below is a brief description from the document:
WebJob is a feature of Azure App Service that enables you to run a program or script in the same instance as a web app, API app, or mobile app. There is no additional cost to use WebJobs.
Run background tasks with WebJobs in Azure App Service
You can create two types of WebJobs, Continuous or Triggered. As the names suggest, a continuous WebJob always run, whereas a triggered WebJob runs on demand or a schedule. In my case, I setup a triggered WebJob to send reports via email on a schedule.
Let’s go over a few things I misunderstood or did not realize about WebJob.
When going over the Microsoft’s documentation on WebJobs, I saw a section on deploying WebJobs using Visual Studio. The section mentions linking a WebJob with a web project such that when you deploy the web project, the web job is already a part of it.
Deploy a project as a WebJob by itself, or link it to a web project so that it automatically deploys whenever you deploy the web project.
WebJobs as .NET Framework console apps
If you want to link a WebJob with a web project, the web job needs to target .NET framework, not .NET core.
.NET Core Web Apps and/or .NET Core WebJobs can’t be linked with web projects. If you need to deploy your WebJob with a web app, create your WebJobs as a .NET Framework console app.
WebJobs as .NET Core console apps
I was confused at first when reading this section. Specially, I was not sure if I need to target .NET framework when building the WebJob if I want to deploy it together with the web app. The answer is No. Note that this section is only for deploying the WebJob using Visual Studios. In other words, the WebJob only needs to target .NET framework if you want to deploy both the WebJob and web app together using Visual Studios. In my case, since I use azure devops, I was able to deploy my WebJob and web app together, both targeting .NET 5.
When trying to deploy the WebJob, I was wondering why the WebJob did not show up in the azure portal when using azure devops to automatically deploy both the WebJob and the web application. However, if I used visual studios to deploy the WebJob, then it showed up correctly. I found online posts such as this StackOverflow and this article and got the wrong impression that the issue had to do with running from package. When using azure app service deployment task, azure devops automatically updates the app service’s config and turn on the WEBSITE_RUN_FROM_PACKAGE flag. I spent a lot of time trying to modify the pipeline to not set the flag, or removed the flag myself, but could not get it to work.
It turned out the issue was that I was using an ubuntu agent to publish the WebJob, which only produced .dll files by default. My WebJob is a .NET 5 console app, and I had to specify the target platform (win-x64) to produce the .EXE file, as shown in the snippet below. When I was using Visual Studios to publish the WebJob, I was using a windows machine, and the result artifact contained the .exe file, which is expected for the WebJob to show up.
- task: DotNetCoreCLI@2 inputs: command: 'publish' publishWebProjects: false projects: '**/WebJob.csproj' arguments: '--configuration $(BuildConfiguration) --output $(Build.ArtifactStagingDirectory)/App_Data/jobs/triggered -r win-x64 --self-contained false' zipAfterPublish: false displayName: 'Publish web job'
My initial plan was to build all the functionalities in the web app and expose them via REST endpoints. However, because both the WebJob and web app run on a same app service, it’s not possible to make HTTP calls from the WebJob to the web app without workarounds. Per the document,
Connection attempts to local addresses (e.g.
Azure WebApp sandboxlocalhost
,127.0.0.1
) and the machine’s own IP will fail, except if another process in the same sandbox has created a listening socket on the destination port. The listening port must be > 1024 and not currently in used.
Below is the exception I got, as stated in the document
Exception Details: System.Net.Sockets.SocketException: An attempt was made to access a socket in a way forbidden by its access permissions 127.0.0.1:...
If you don’t set the AzureWebJobsDashboard configuration in the azure app service, you may see some warnings in the azure WebJob dashboard, as shown below.
However, this is just a warning and does not cause any harm. As per this github issue, it appears as if this config is necessary only for dashboard logging using azure blob storage. In my case, I was able to log to AppInsights, so I did not have to set the config. However, I also noticed that the dashboard only displays up to a certain number of logs. So, you may want to set the config if you want to persist logs using azure blob storage, or setup AppInsights logging.
The fundamental problem that you’re running into is that the webjobs dashboard UX cannot be be used for webjobs 3.x onwards and functions 2.x onwards. It has been replaced with App Insights integration. We are no longer making any engineering investments into this UX.
Make sure that you are setting a connection string named AzureWebJobsDashboard
I setup my WebJob to run on a schedule, by specifying the cron expression in the Settings.job file. At first, I thought it did not run at the right time that I expected based on the cron expression. I realized the issue is because the server’s time was in UTC. I got the WebJob to run correctly at the setup scheduled by adding the WEBSITE_TIME_ZONE configuration to the app service. See this link for for more info.
Now that I have gone over some of the misunderstandings I had about WebJob, in the next section, I’ll quickly go over the project and pipeline setup.
The WebJob is basically a .NET 5 console application. I use the host builder and configure dependency injection, load configurations from json files, environment variables, key vault, configure AppInsights logging etc … For instructions on how to develop the WebJob using WebJobs SDK, checkout the document. One caveat is that I could not get the correct environment name from the HostingEnvironment of the HostBuilderContext. I used the Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT")
to retrieve the environment.
// codes omitted for brevity using Microsoft.Azure.WebJobs; namespace WebJob { public class Program { private static IConfigurationRoot _configuration; static async Task Main(string[] args) { var builder = new HostBuilder(); builder.ConfigureWebJobs(b => { }); builder.ConfigureAppConfiguration((context, config) => { ConfigureAppConfiguration(config, context); }); builder.ConfigureLogging((context, loggingBuilder) => { loggingBuilder.AddConsole(); string appInsightsInstrumentationKey = _configuration["APPINSIGHTS_INSTRUMENTATIONKEY"]; if (!string.IsNullOrEmpty(appInsightsInstrumentationKey)) { Console.WriteLine("Configuring applicationInsights logging."); loggingBuilder.AddApplicationInsightsWebJobs(o => { o.InstrumentationKey = appInsightsInstrumentationKey; }); } }); builder.ConfigureServices(services => { ConfigureServices(services); }); var host = builder.Build(); using (host) { var jobHost = host.Services.GetService(typeof(IJobHost)) as JobHost; // codes omitted for brevity var paymentReportJob = new PaymentReportJob(...); var inputs = new Dictionary<string, object> { { "reportJob", paymentReportJob } }; await host.StartAsync(); await jobHost.CallAsync("DoReport", inputs); await host.StopAsync(); } } [NoAutomaticTrigger] public static async Task DoReport(PaymentReportJob reportJob) { await reportJob.SendReportAsync(); } private static void ConfigureServices(IServiceCollection services) { services.Configure<SmtpConfigOptions>(_configuration.GetSection("SmtpConfigOptions")); // codes omitted for brevity } private static void ConfigureAppConfiguration(IConfigurationBuilder config, HostBuilderContext context) { var environment = Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT"); Console.WriteLine("Environment: " + environment); config.AddJsonFile("appsettings.WebJob.json", optional: false, reloadOnChange: true); // codes omitted for brevity _configuration = config.Build(); } } // codes omitted for brevity }
Below snippets contain just the relevant steps for building the WebJob and package it together with the web application in a single artifact, ready for deploying to the app service.
trigger: - "master" pool: vmImage: 'ubuntu-latest' variables: solution: '**/*.sln' buildPlatform: 'Any CPU' buildConfiguration: 'Release' steps: - checkout: self # some codes omitted for brevity - task: DotNetCoreCLI@2 inputs: command: 'publish' publishWebProjects: true modifyOutputPath: false arguments: '--configuration $(BuildConfiguration) --output $(Build.ArtifactStagingDirectory)' zipAfterPublish: false displayName: 'Publish web project' - task: DotNetCoreCLI@2 inputs: command: 'publish' publishWebProjects: false projects: '**/WebJob.csproj' arguments: '--configuration $(BuildConfiguration) --output $(Build.ArtifactStagingDirectory)/App_Data/jobs/triggered -r win-x64 --self-contained false' zipAfterPublish: false displayName: 'Publish web job' - task: ArchiveFiles@2 inputs: rootFolderOrFile: '$(Build.ArtifactStagingDirectory)' includeRootFolder: false archiveType: 'zip' archiveFile: '$(Build.ArtifactStagingDirectory)/$(Build.BuildId).zip' - task: PublishPipelineArtifact@1 inputs: targetPath: '$(Build.ArtifactStagingDirectory)' ArtifactName: 'drop' displayName: 'Publish artifact'
In the above code snippets, notice that:
I did not have to do anything special in the release pipeline. This is because the WebJob is just part of the web app, and the build pipeline put everything together in a single zip file.
Run background tasks with WebJobs in Azure App Service
Error generated when executable project references mismatched executable
Making requests to localhost within Azure App Services application
How do I set the server time zone for my web app?
Make sure that you are setting a connection string named AzureWebJobsDashboard
Building a fully multitenant system using Microsoft Identity Framework and SQL Row Level Security
Azure AD B2B vs Azure AD B2C
Analyzing a rental property through automation
Web scraping in C# using HtmlAgilityPack
Building multitenant application – Part 2: Storing value into database session context from ASP.NET core web API
Authenticate against azure ad using certificate in a client credentials flow
Notes on The Clean Architecture
Three essential libraries for unit testing a .NET project