Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Generic HostBuilder and No Job function found #2146

Open
ranouf opened this issue Mar 16, 2019 · 1 comment
Open

Generic HostBuilder and No Job function found #2146

ranouf opened this issue Mar 16, 2019 · 1 comment

Comments

@ranouf
Copy link

ranouf commented Mar 16, 2019

Hi,

I have 3 differents WebJobs which call different apis using a specific parameter in the uri.

  • Webjob1 => parameter = "Paris"
  • WebJob2 => parameter = "Tokyo"
  • WebJob3 => parameter = "London"

The rest of the code is the same, I want 3 different webjobs to be able to scale them independantly depending of the CPU charge.

When I first created the first Webjob, the timetrigger function was correctly found.
When it becames the time to create a generic HostBuilder, the timetrigger function is never found and I get this error message:

  No job functions found. Try making your job classes and methods public. If you're using binding extensions (e.g. Azure Storage, ServiceBus, Timers, etc.) make sure you've called the registration method for the extension(s) in your startup code (e.g. builder.AddAzureStorage(), builder.AddServiceBus(), builder.AddTimers(), etc.).

It seems not possible to bind a [Time/Queue/...]Trigger function from a generic HostBuilder which is in a different dll? Is there a way to specify in which dll the HostBuilder must search for [Time/Queue/...]Trigger functions?

Webjob.Generic > Program.cs

 public abstract class PollerHostBuilder<TLogger, TMyQueueService, TPollerModule>
        where TLogger : ILogger
        where TMyService : IQueueService
        where TMyModule : MyModule
    {
        public static IHost GenerateHost(string[] args)
        {
            //source: https://docs.microsoft.com/en-us/aspnet/core/fundamentals/host/generic-host?view=aspnetcore-2.2
            return new HostBuilder()
                .ConfigureHostConfiguration(configHost =>
                {
                    configHost.SetBasePath(Directory.GetCurrentDirectory());
                    configHost.AddJsonFile("hostsettings.json", optional: true);
                    configHost.AddEnvironmentVariables();
                    configHost.AddCommandLine(args);
                })
                .ConfigureWebJobs(webJobConfiguration =>
                {
                    webJobConfiguration.AddTimers();
                    webJobConfiguration.AddAzureStorageCoreServices();
                })
                .ConfigureAppConfiguration((hostContext, configApp) =>
                {
                    configApp.AddJsonFile("appsettings.json", optional: true);
                    configApp.AddJsonFile($"appsettings.{hostContext.HostingEnvironment.EnvironmentName}.json", optional: true);
                    configApp.AddEnvironmentVariables();
                    configApp.AddCommandLine(args);
                })
                .UseServiceProviderFactory(new AutofacServiceProviderFactory())
                .ConfigureServices((hostContext, services) =>
                {
                    // Automapper
                    services.AddAutoMapper(c => { c.AddProfile<MyProfile>(); });

                    services.ConfigureAndValidate<MySettings>(hostContext.Configuration);
                    services.ConfigureAndValidate<ConnectionStringsSettings>(hostContext.Configuration);

                    // Setup SQLServerDB
                    services.AddDbContext<MyContext>(options =>
                        options.UseSqlServer(
                            hostContext.Configuration.GetConnectionString("DefaultConnection"),
                            opt => opt
                                .UseNetTopologySuite()
                                .EnableRetryOnFailure(
                                    maxRetryCount: 5,
                                    maxRetryDelay: TimeSpan.FromSeconds(30),
                                    errorNumbersToAdd: null
                                )
                                .CommandTimeout(hostContext.HostingEnvironment.IsDevelopment()
                                    ? 60 // my machine could be very slow sometimes ...
                                    : 30
                                )
                        )
                    );

                    // Identity
                    services.AddIdentity<MyUser, Role>()
                        .AddEntityFrameworkStores<MyContext>()
                        .AddDefaultTokenProviders()
                        .AddUserStore<UserStore<MyUser, Role, MyContext, Guid, IdentityUserClaim<Guid>, UserRole, IdentityUserLogin<Guid>, IdentityUserToken<Guid>, IdentityRoleClaim<Guid>>>()
                        .AddRoleStore<RoleStore<Role, MyContext, Guid, UserRole, IdentityRoleClaim<Guid>>>();
                })
                .ConfigureContainer<ContainerBuilder>(builder =>
                {
                    builder.RegisterModule<TMyModule>();
                })
                .ConfigureLogging((hostContext, configLogging) =>
                {
                    configLogging.AddApplicationInsights(c =>
                        c.InstrumentationKey = hostContext.Configuration["ApplicationInsights:InstrumentationKey"]
                    );
                    if (hostContext.HostingEnvironment.IsDevelopment())
                    {
                        configLogging.AddConsole();
                        configLogging.AddDebug();
                        configLogging.AddFile();
                        configLogging.AddFilter("Microsoft", LogLevel.Warning);
                    }
                })
                .UseConsoleLifetime()
                .Build();
        }
}

WebJob.Paris >Program.cs:

public class Program : PollerHostBuilder<ILogger<Program>, IMyService, MyParisModule>
    {
        public static async Task Main(string[] args)
        {
            var host = GenerateHost(args);

            using (host)
            {
                await host.RunAsync();
            }
        }
    }

WebJob.Paris > Functions.cs

public class MyParisFunctions 
 {

     public MyParisFunctions()
     {
     }

     public async Task MyParisFunctionAsync([TimerTrigger("0 */2 * * * *", RunOnStartup = true)]TimerInfo _)
     {
         //Do something
     }
@admalledd
Copy link

Just ran into basically the same problem. I have a "BaseWorker" which itself doesn't have any functions but sets up and builds many other things, and a few different specific workers that each have their own (webjob triggered) functions. However the "specific workers" only transiently reference the WebJobs SDK. None of the functions were being found.

It seems to be root-caused by the DefaultTypeLocator not looking "down" references of references? Is there maybe a way to add, replace or extend without a full replacing the TypeLocator? My work around is in each of the Program.cs files where my functions/triggers actually live to static-ref a SDK class but that feels bad.

internal class Program
{
    [System.Diagnostics.CodeAnalysis.SuppressMessage("CodeQuality", "IDE0051:Remove unused private members", 
            Justification = "Due to auto-magic type finding, we have to strongly reference a type from either M.A.WJ or M.A.WJ.Hosting. See https://github.com/Azure/azure-webjobs-sdk/issues/2146")]
    private static readonly Microsoft.Azure.WebJobs.Hosting.JobHostService _ignoreMe;
    //...
}

Or copy/paste the above DefaultTypeLocator() and add it (with different helpful name) to your "Base" project, since it has a "extension assemblies" on line 66 that will thus use anything directly referencing your assembly.

Longer term, I think that the SDK's type locator should try and dig just a little bit deeper, and maybe "always" include the Entry Assembly? Or let us add (somehow in ConfigureServices) / specify additional Assemblies / Types to search/index without having to write our own full type locator.

PS: debugging this far was a decent pain due to the embedded debug symbols being partial and thus hiding most of this. Had to butcher a local copy to emit full debug symbols. Can there please be an easier way to include/use full symbols?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants