Azure / azure-functions-dotnet-worker

Azure Functions out-of-process .NET language worker
MIT License
419 stars 182 forks source link

ServiceBusTrigger and INameResolver #393

Open dhermyt opened 3 years ago

dhermyt commented 3 years ago

Hello,

Is it still possible to pass dynamic queue name to Service Bus trigger? Previously INameResolver was used for that, but the new extension is missing this interface.

public class QueueNameResolver : INameResolver
{
    public string Resolve(string name)
    {
        return ConfigurationManager.AppSettings[name].ToString();
    }
}
fabiocav commented 3 years ago

Can you share more details about your scenario?

Are you trying to programmatically set those values? This is not supported with the isolated worker, and something that would cause problems in consumption environments, so we just want to better understand your requirements. Thanks!

dhermyt commented 3 years ago

Hello Fabio,

In my case the queue name comes from the configuration and should be set dynamically. This is how it looked in the previous SDK:

private const string InputQueueName = "%ServiceBus:Queues:QueueName%";

        [Function(FunctionName)]
        public async Task RunAsync([ServiceBusTrigger(InputQueueName)] Message message, CancellationToken cancellationToken)
        {

Here is the example on SO: https://stackoverflow.com/a/30515085

fabiocav commented 3 years ago

Thank you for the additional details.

This is indeed a limitation with the model, currently, as you don't have the ability to inject a name resolver in the context used by the extension.

Flagging this so we can discuss an appropriate approach to meet the requirement.

fabiocav commented 3 years ago

@ankitkumarr FYI, this would benefit from the worker driven indexing/metadata work.

tjrobinson commented 3 years ago

This is a blocker for us moving from Web Jobs to Azure Functions.

christianarg commented 3 years ago

@dhermyt I totally agree we should be able to inject our custom NameResolver like we can with the "normal" Azure Functions (called now class library AFAIK).

https://docs.microsoft.com/en-us/azure/app-service/webjobs-sdk-how-to#version-3x-6

 var builder = new HostBuilder();
    var resolver = new CustomNameResolver();
    builder.ConfigureWebJobs(b =>
    {
        b.AddAzureStorageCoreServices();
    });
    builder.ConfigureServices(s => s.AddSingleton<INameResolver>(resolver));  // This is how to do it with "normal" azure functions and WebJobs
    var host = builder.Build();
    using (host)
    {
        await host.RunAsync();
    }

But have in mind that the Isolated mode does implement a the default NameResolver es mentioned here https://docs.microsoft.com/en-us/azure/app-service/webjobs-sdk-how-to#custom-binding-expressions

"There's a default NameResolver that takes effect if you don't provide a custom one. The default gets values from app settings or environment variables."

So, while it's not as powerful and flexible as a custom NameResolver, it may work for you as the default behaviour is reading from an appsetting/enviromentvariable.

So this:

        [Function("Function1")]
        public void Run([ServiceBusTrigger("%myqueueName%", Connection = "MyConnection")] string myQueueItem, FunctionContext context)
        {
            var logger = context.GetLogger("Function1");
            logger.LogInformation($"C# ServiceBus queue trigger function processed message: {myQueueItem}");
        }

reads the queue name from a configuration setting / enviroment variable called myqueueName. I have tested this myself both locally and in Azure.

I insist, we should be able to inject our custom NameResolver for max flexibility

iRubens commented 1 year ago

This is a blocker for us moving from Web Jobs to Azure Functions.

It's the same for us, we're planning to migrate in-process functions to isolated and this is another roadblock we need to overcome. @fabiocav is there any plan to port this feature?

ankithkonda commented 1 year ago

Hi @fabiocav, is there any public information/github issue on worker driven indexing/metadata work?

alexjamesbrown commented 9 months ago

Is there any update here? It seems I can't even put my queue names in a separate 'queue-names.json' file

eg

    builder
        .AddJsonFile("local.settings.json", optional: true, reloadOnChange: true)
        .AddJsonFile("queue-names.json", optional: true, reloadOnChange: true)
        .AddEnvironmentVariables();

If the queue names are in queue-names.json then it simply doesn't work. They have to be in local.settings.json

JohnWilson-PandSD commented 8 months ago

I've also tried several variations of trying to inject a custom INameResolver (with a dotnet-isolated) function before I came across this github issue. As with others, it always hits the internal name resolver first, barfs and then stops, before getting to mine, by which time it's too late.

Given that .NET 8 is forcing Functions into being isolated process (and no more in-process), it would be good if we could get this resolved ;)

Thank you 👍🏻

alexjamesbrown commented 8 months ago

I've also tried several variations of trying to inject a custom INameResolver (with a dotnet-isolated) function before I came across this github issue. As with others, it always hits the internal name resolver first, barfs and then stops, before getting to mine, by which time it's too late.

Given that .NET 8 is forcing Functions into being isolated process (and no more in-process), it would be good if we could get this resolved ;)

Thank you 👍🏻

I agree - I'm going to try and do a blog post on how I worked around this - but it's convoluted, build step, generate local.settings.json for each user. Honestly, such a lot of work for something that should be so simple!

prakash-gudipati commented 7 months ago

I've also tried several variations of trying to inject a custom INameResolver (with a dotnet-isolated) function before I came across this github issue. As with others, it always hits the internal name resolver first, barfs and then stops, before getting to mine, by which time it's too late. Given that .NET 8 is forcing Functions into being isolated process (and no more in-process), it would be good if we could get this resolved ;) Thank you 👍🏻

I agree - I'm going to try and do a blog post on how I worked around this - but it's convoluted, build step, generate local.settings.json for each user. Honestly, such a lot of work for something that should be so simple!

what is the solution that you have implemented? I am pretty much stuck with the same issue. I am unable to resolve the name for the expression from appSettings

MrBildo commented 2 months ago

I just ran into this today. At least I'm not alone.

My scenario is pretty simple. I have a TimerTrigger with a CRON time. I know I can add that to the environment variables and it will work. What I want(ed) was to be a little smarter about it and provide a default.

builder.Configuration["SCHEDULER_CRON_TIME"] = builder.Configuration["SCHEDULER_CRON_TIME"] ?? "*/1 * * * * *";

However, that doesn't work, because the internal Function logic doesn't seem to be playing off the same configuration.

Like others I tracked this down to the name resolver, in my case, TimerSchedule.cs:

string resolvedExpression = nameResolver.ResolveWholeString(attribute.ScheduleExpression);

This seems like a really basic thing. As far as I can tell the Azure Function stuff has it's own internal service registration, because I don't even see DefaultNameResolver in the service collection.

well0549 commented 1 month ago

I also ran into this issue. I sthere any change planned ?

ac931274 commented 1 month ago

Hi there, I ran into this problem yesterday and dug into it.
1) From what I can see the resolving of the %token% value from Environment variables is done in Func.exe / the Host in Azure Functions.
2) The Metadata that is needed for the runner to register with all the queue etc is sent across via a GRPC call on start up. 3) This metadata is generated at design time by a Code Generator and is in a class which implements IFunctionMetadataProvider.

I have been able to create a new class internal class ConfigurableFunctionMetadataProvider(IFunctionMetadataProvider baseProvider, IConfiguration configuration, ILogger<ConfigurableFunctionMetadataProvider> logger) : IFunctionMetadataProvider { .... } which then decorates the existing entry in the ServiceCollection services.Decorate<IFunctionMetadataProvider, ConfigurableFunctionMetadataProvider>(); (via scrutor) and walks the metadata coming back and replacing the tokens from what is configured in IConfiguration before passing it on.

I would just paste the code here but it is entwined in a customer project and I would have to rewrite it afresh if others were going to use it. If people need it let me know and I will invest the time to do that for you. Or ask on SO and link the question after this message and I will update there for you (and others).

Jonathan

alexjamesbrown commented 1 month ago

@ac931274 sounds really interesting - I'd be curious to see it if at all possible: I ended up doing some WILD post build script to create user-specific queues and change local.settings.json

Here's the node.js script

const fs = require('fs');
const os = require('os');

const settingsPath = '../src/Function/local.settings.json';
const serviceBusQueueNamesPath = '../infra/config/servicebus-queue-names.json';

const clean = (input) => input.replace(/-/g, '');

const hostname = os.hostname().split('.')[0];
const user = os.userInfo().username;
const prefix = `${clean(user)}-${clean(hostname)}-`;

if (!fs.existsSync(settingsPath)) {
    console.warn('local.settings.json path does not exist ('+settingsPath+')');
    process.exit(1);
}

if (!fs.existsSync(serviceBusQueueNamesPath)) {
    console.error('ServiceBus Queue Names path does not exist ('+settingsPath+')');
    process.exit(1);
}

// I have the queue names stored in a json file
// (nb: this is also for deployment, not just this)
const serviceBusQueueNames = JSON.parse(fs.readFileSync(serviceBusQueueNamesPath, 'utf8'));
const localSettings = JSON.parse(fs.readFileSync(settingsPath, 'utf8'));

for (const key in serviceBusQueueNames) {
    localSettings.Values[key] = prefix + serviceBusQueueNames[key];
}

fs.writeFileSync(settingsPath, JSON.stringify(localSettings, null, 2), 'utf8');
console.log('local.settings.json updated successfully.');

And I call this in the csproj like this

    <Target Name="UpdateLocalSettings" BeforeTargets="Build" Condition="Exists('$(ProjectDir)local.settings.json')">
        <Exec Command="node UpdateLocalSettings.js" WorkingDirectory="$(SolutionDir)tools" />
    </Target>

.. as I said, quite wild, but had to get something working

borigas commented 1 month ago

@ac931274 I am also interested in seeing your workaround to this. This issue is blocking our migration to isolated worker, which is a little scary since they're saying in process isn't going to be updated anymore.

ac931274 commented 1 month ago

I have added my code to Stack Overflow here : https://stackoverflow.com/questions/78897966/azure-functions-trigger-variables-isolated-functions/78897967#78897967

Please upvote, question and answer, if useful.