Azure Function .NET5 (isolated) throws when using [EventGridTrigger] EventGridEvent as parameter - c#

After upgrade to .NET5 for Azure Function this signature throws the exception below.
I've implemented it according to the documentation here https://learn.microsoft.com/en-us/azure/azure-functions/functions-bindings-event-grid-trigger?tabs=csharp%2Cbash
[Function(nameof(CustomerCreated))]
public async Task CustomerCreated([EventGridTrigger] EventGridEvent eventGridEvent, FunctionContext functionContext)
{
// My implementation
}
System.NotSupportedException: 'Deserialization of types without a
parameterless constructor, a singular parameterized constructor, or a
parameterized constructor annotated with 'JsonConstructorAttribute' is
not supported. Type 'Azure.Messaging.EventGrid.EventGridEvent'. Path:
$ | LineNumber: 0 | BytePositionInLine: 1.'
How can I receive the event from EventGrid?

The new way of doing this seems to be CloudEvent from the Azure.Messaging namespace.
(A side note is that the data properties are case-sensitive, I'm not sure if they were before.)
public async Task CustomerCreated([EventGridTrigger] CloudEvent eventGridEvent, FunctionContext functionContext)
{
// [...]
}
UPDATE:
This breaks when deploying to Azure and receiving events from a real event grid with an Azure Function as endpoints. But works fine when testing locally using ngrok and webhook. Back to square one.
After endless googling with outdated examples I found a note in this document, pointing to this example showing that you need to create a custom type (looking just like the EventGridEvent).
After testing I found that this actually works:
Pass a string instead of EventGridEvent and parse the actual event with the utility function from EventGridEvent.
[Function(nameof(CustomerCreated))]
public async Task CustomerCreated([EventGridTrigger] string data, FunctionContext functionContext)
{
var eventGridEvent = EventGridEvent.Parse(BinaryData.FromString(data));
// Rest of code
}
Another related caveat is that you can't create the function from the portal but need to run it from the command line like:
az eventgrid event-subscription create --name customer-created \
--source-resource-id <event-grid-topic-resource-id> \
--endpoint <azure-func-resource-id> --endpoint-type azurefunction
(The resource id:s can be found at http://resources.azure.com/)
Hello Microsoft;
Running Azure Functions on .NET5 doesn't feel very GA with this approach. I really hope to be able to pass typed objects like EventGridEvent from other Azure services with official libraries.
I really think that the EventGridEvent on .NET5/dotnet-isolated should be compatible with Azure Functions at the same level as on previous .NET versions and as in the public examples.

Related

Add Custom Properties for RequestTelemetry of the Azure Function (v3) binding to BlobTrigger

I want to add Custom Properties to the RequestTelemetry generated for the Azure function(V3) written in C#.
Thanks to this StackOverflow post, I managed to achieve this for the function with HttpTrigger binding.
var requestTelemetry = req.HttpContext?.Features.Get<RequestTelemetry>();
requestTelemetry.Properties.Add("MyProp", "Some value");
However, when I try to do same thing for another function with BlobTrigger binding, it became confusing. The first challenge is:
How to get current RequestTelemetry in a function that is using
BlobTrigger binding?
In a function with HttpTrigger binding, the function is injected with HttpRequest parameter
public async Task<IActionResult> Upload(
[HttpTrigger(AuthorizationLevel.Anonymous, "get", "post", Route = "upload/{name}")] HttpRequest req,
string name,
ILogger log,
ExecutionContext context)
{
...
}
So we can get current RequestTelemetry using HttpRequest. However, what about a function with BlobTrigger:
[FunctionName("Create-Thumbnail")]
public async Task CreateThumbnail([BlobTrigger("input/{name}", Source = BlobTriggerSource.EventGrid, Connection = "AzureWebJobsStorage")] Stream image,
IDictionary<string,string> metadata,
string name,
ExecutionContext context)
{
...
}
I have tried injecting HttpRequest and using same way as HttpTrigger function. But it didn't work.
After hours extensive research, I couldn't find any documentation or posts that are related to this question.
Can anyone provide some tips for this?
AFAIK there is no http request when using a blob trigger. You can still add custom properties to the telemetry generated during the execution by setting properties like this:
// The current telemetry item gets a property.
// Useful if the function is not triggered by an http request
Activity.Current.AddTag("setUsingTag", "setUsingTag");
// Subsequent telemetry gets this property attached
Activity.Current.AddBaggage("setUsingActivityBaggage", "setUsingActivityBaggage");
When using Baggage instead of a Tag the custom property is added to all telemetry generated during the execution, like dependency calls etc.
See also this github thread. It also mentions there might be a bug introduced in a later version of AI that might force you to downgrade AI for this to work.
Thanks to this GitHub issue, this is finally working after downgrading System.Diagnostics.DiagnosticSource to version 4.6
<PackageReference Include="System.Diagnostics.DiagnosticSource" Version="4.6.0" />

How to make startup Azure Function

I have a Azure Function like that
[FunctionName("Function1")]
public static void Run([ServiceBusTrigger("myqueue", AccessRights.Manage, Connection = "AzureWebJobsServiceBus")]string myQueueItem, TraceWriter log)
{
log.Info($"C# ServiceBus queue trigger function processed message: {myQueueItem}");
}
I want to dynamic bind myqueue and AzureWebJobServiceBus connection string in a startup or OnInit of app without as method's parameter above. I mean, I want to a method run first of all like Program.cs in WebJob to binding or start up global variables. Can I do that in Azure Function and how to do it?
Many thanks
The attributes here are compiled into a function.jsonfile before deployment that has the info on what the binding talks to. Often things like the connection string reference app settings. Neither of these can be modified within the code itself (so a Program.cs couldn’t modify the function.json binding).
Can you share any more on your scenario? If you have multiple queues you want to listen to could you deploy a function per queue? Given the serverless nature of Functions there isn’t a downside to having extra functions deployed. Let me know - happy to see if we can help with what you need.
Edit
The suggestion below doesn't work for a Trigger, only for a Binding.
We have to wait for the team to support Key Vault endpoints in Azure Functions, see this GitHub issue.
I think what you are looking for is something called Imperative Bindings.
I've discovered them myself just yesterday and had a question about them also. With these type of bindings you can just dynamically set up the bindings you want, so you can retrieve data from somewhere else (like a global variable, or some initialization code) and use it in the binding.
The thing I have used it for is retrieving some values from Azure Key Vault, but you can also retrieve data from somewhere else of course. Some sample code.
// Retrieving the secret from Azure Key Vault via a helper class
var connectionString = await secret.Get("CosmosConnectionStringSecret");
// Setting the AppSetting run-time with the secret value, because the Binder needs it
ConfigurationManager.AppSettings["CosmosConnectionString"] = connectionString;
// Creating an output binding
var output = await binder.BindAsync<IAsyncCollector<MinifiedUrl>>(new DocumentDBAttribute("TablesDB", "minified-urls")
{
CreateIfNotExists = true,
// Specify the AppSetting key which contains the actual connection string information
ConnectionStringSetting = "CosmosConnectionString",
});
// Create the MinifiedUrl object
var create = new CreateUrlHandler();
var minifiedUrl = create.Execute(data);
// Adding the newly created object to Cosmos DB
await output.AddAsync(minifiedUrl);
There are also some other attributes you can use with imperative binding, I'm sure you'll see this in the docs (first link).
Instead of using Imperative Bindings, you can also use your application settings.
As a best practice, secrets and connection strings should be managed using app settings, rather than configuration files. This limits access to these secrets and makes it safe to store function.json in a public source control repository.
App settings are also useful whenever you want to change configuration based on the environment. For example, in a test environment, you may want to monitor a different queue or blob storage container.
App settings are resolved whenever a value is enclosed in percent signs, such as %MyAppSetting%. Note that the connection property of triggers and bindings is a special case and automatically resolves values as app settings.
The following example is an Azure Queue Storage trigger that uses an app setting %input-queue-name% to define the queue to trigger on.
{
"bindings": [
{
"name": "order",
"type": "queueTrigger",
"direction": "in",
"queueName": "%input-queue-name%",
"connection": "MY_STORAGE_ACCT_APP_SETTING"
}
]
}

Power BI Embedded PostImportWithFile returning BadRequest

I'm looking to post a PBIX file up to a workspace through the .NET API using the PostImportWithFile method of the PowerBiClients Imports object. The code is pretty much identical to that seen in option 6 of the Provision Sample (see https://github.com/Azure-Samples/power-bi-embedded-integrate-report-into-web-app/blob/master/ProvisionSample/Program.cs).
There is a workspace collection and a workspace that have been created. The workspace was created through code using the relevant API methods so I know that the authentication side of things is working correctly.
When I call the PostImportWithFile method I'm getting a BadRequest exception being thrown. To verify that this wasn't something to do with my code I've compiled and run the ProvisionSample and selected option 6 and selected the same file and received the same result.
I'm supplying null for the dataset parameter, which is optional and defaults to null anyway, so I can't see this being the cause of my issues.
I've been unable to find anything online regarding this method and a BadRequest so was wondering if there was anyone with experience with this API that had run into something similar?
The PBIX file works fine through Power BI Services, so I'm assuming nothing is wrong with the file.
Based on the documentation here it looks like you would need to supply a datasetname, it does not look like it is optional.
public static Task<Import> PostImportWithFileAsync(
this IImports operations,
string collectionName,
string workspaceId,
Stream fileStream,
string datasetDisplayName,
Nullable<int> nameConflict = null,
CancellationToken cancellationToken = null)
Non async version here also looks like datasetdisplayname is not optional.
Hope this helps.

How can I make url path in Swashbuckle/Swaggerwork when api is served from inside another project?

all. I am trying to document a WebApi 2 using Swashbuckle package.
All works great if the API is running by itself i.e. localhost/api/swagger brings me to ui and localhost/api/swagger/docs/v1 to json.
However the producation app initializes this same Webapi project by running webapiconfig method of this project from global.asax.cs in another - now web project (the main application one). So the api url looks like localhost/web/api instead of localhost/api.
Now swashbuckle doesn't work like that at all.
localhost/api/swagger generates error cannot load
'API.WebApiApplication', well of course
localhost/web/swagger = 404
localhost/web/api/swagger = 404
I tried to look everywhere, but all I found is workaround.
c.RootUrl(req => req.RequestUri.GetLeftPart(UriPartial.Authority) + VirtualPathUtility.ToAbsolute("~/").TrimEnd('/'));
Unfortunately it doesn't work, now maybe it should and I just need to change something but I don't even know what exactly this property expects and what it should be set to.
May be it's not even applicable - maybe setup we have requires something else or some swashbuckle code changes.
I will appreciate any help you can provide. I really starting to like swagger (and swashbuckle) for rest documentation.
For Swashbuckle 5.x:
This appears to be set by an extension method of httpConfiguration called EnableSwagger. Swashbuckle 5.x migration readme notes that this replaces SwaggerSpecConfig. SwaggerDocConfig RootUrl() specifically replaces ResolveBasePathUsing() from 4.x.
This practically works the same as it did before, looks like the biggest change was that it was renamed and moved into SwaggerDocConfig:
public void RootUrl(Func<HttpRequestMessage, string> rootUrlResolver)
An example from the readme, tweaked for brevity:
string myCustomBasePath = #"http://mycustombasepath.com";
httpConfiguration
.EnableSwagger(c =>
{
c.RootUrl(req => myCustomBasePath);
// The rest of your additional metadata goes here
});
For Swashbuckle 4.x:
Use SwaggerSpecConfig ResolveBasePathUsing and have your lambda read your known endpoint.
ResolveBasePathUsing:
public SwaggerSpecConfig ResolveBasePathUsing(Func<HttpRequestMessage, string> basePathResolver);
My API is behind a load balancer and this was a helpful workaround to providing a base address. Here's a dumb example to use ResolveBasePathUsing to resolve the path with a known base path.
string myCustomBasePath = #"http://mycustombasepath.com";
SwaggerSpecConfig.Customize(c =>
{
c.ResolveBasePathUsing((req) => myCustomBasePath);
}
I hardcoded the endpoint for clarity, but you can define it anywhere. You can even use the request object to attempt to cleanup your request uri to point to /web/api instead of /api.
The developer commented on this workaround on GitHub last year:
The lambda takes the current HttpRequest (i.e. the request for a given
Swagger ApiDeclaration) and should return a string to be used as the
baseUrl for your Api. For load-balanced apps, this should return the load-balancer path.
The default implementation is as follows:
(req) => req.RequestUri.GetLeftPart(UriPartial.Authority) + req.GetConfiguration().VirtualPathRoot.TrimEnd('/');
...
Re relative paths, the Swagger spec requires absolute paths because
the URL at which the Swagger is being served need not be the URL of
the actual API.
...
The lambda is passed a HttpRequestMessage instance ... you should be able to use this to get at the RequestUri etc. Another option, you could just place the host name in your web.config and have the lambda just read it from there.

ODataClient MaxProtocolVersion V3

I am trying to consume OData from a windows forms. So, what i have done to now is create a new project, i added a web service reference to the OData service and try to consume it.
My code is:
var VistaEntities = new VrExternalEntities("serviceURI");
var query = VistaEntities.VRtblCinemaType
.Where(
x =>
x.VRtblCinema_Operators
.Any
(
z =>
z.VRtblSessions
.Any
(
y =>
y.Session_dtmDate_Time > DateTime.Now
)
)
)
.Select
(
x =>
new
{
x.CinType_strCode,
x.CinType_strDescription
}
);
If i remove the Where clause it works. If i do it says that Any is not supported. I know i have to set MaxProtocolVersion to V3 but i do not know how to do it. I don't have an entity context or anything else. I only have what i have stated above.
Please provide steps on how to accomplish that.
Thanks in advance.
Giannis
You must retrieve the configuration of your DataService and set the MaxProtocolVersion of its behavior to V3.
The best place to do this is certainly in the InitializeService static method you can define in your service class, which will be given the proper configuration object as its config parameter by the environment. It will only be invoked once, typically at the first request.
Note #1: You need WCF Data Services 5.0 or greater. The best way to get it is probably via the Server NuGet package.
Note #2: Oddly enough, the DataServiceProtocolVersion type, although in the Common namespace, is included in the Client assembly (Microsoft.Data.Services.Client, provided by the Client NuGet package). I suggested a better organization here.
public class Vista : DataService<VistaContext>
{
public static void InitializeService(DataServiceConfiguration config)
{
config.SetEntitySetAccessRule(...);
config.DataServiceBehavior.MaxProtocolVersion = DataServiceProtocolVersion.V3;
...
}
}
Update:
The client may indeed specify the desired version in the requests by using the DataServiceVersion HTTP header. It's currently recommended that you specify and support a range of versions using the MinDataServiceVersion and MaxDataServiceVersion headers if you can, for obvious reasons. Note however that the MinDataServiceVersion will be removed in OData 4.0 (see appendix E.1 of part 1 and "What's new" documents drafts).
The relevant documentation for the WCF Data Services 5.x implementation is available here. The documentation specific to the client seems pretty scarce, but looking at the reference you can see that you must use this constructor for the DataServiceContext to specify the maximum protocol version, and it looks like you cannot change it at any one point for subsequent requests without rebuilding a new context. You may attempt to fiddle with the headers directly, but I wouldn't expect it to work reliably (or at all).
So, to answer your question, you really need control over how you create the context for the client.

Categories

Resources