Referring to the following example:
public static void Run([CosmosDBTrigger(
databaseName: "ToDoItems",
collectionName: "Items",
ConnectionStringSetting = "CosmosDBConnection",
LeaseCollectionName = "leases",
CreateLeaseCollectionIfNotExists = true)]IReadOnlyList<Document> documents,
ILogger log)
I understand, the connectionStringSetting isn't the connection string to use, rather it's name of the setting to look up containing the ConnectionString.
Will this also work for CollectionName and databasename as well? I understand I can experiment and figure out, but I am confused as to how this is even resolved at build time/deployment time?
I see several properties being assigned values while others are taking them from configuration? Is it the underlying constructor for CosmosDBTrigger which takes care of using appropriate value?
Binding to a function is a way of declaratively connecting another resource to the function; bindings may be connected as input bindings, output bindings, or both. Data from bindings is provided to the function as parameters.
here is small sample of Azure function using CosmosDB trigger that is invoked when there are inserts or updates in the specified database and collection.
using Microsoft.Azure.Documents;
using Microsoft.Azure.WebJobs;
using Microsoft.Azure.WebJobs.Host;
using System.Collections.Generic;
using Microsoft.Extensions.Logging;
namespace CosmosDBSamplesV2
{
public static class CosmosTrigger
{
[FunctionName("CosmosTrigger")]
public static void Run([CosmosDBTrigger(
databaseName: "ToDoItems",
collectionName: "Items",
ConnectionStringSetting = "CosmosDBConnection",
LeaseCollectionName = "leases",
CreateLeaseCollectionIfNotExists = true)]IReadOnlyList<Document> documents,
ILogger log)
{
if (documents != null && documents.Count > 0)
{
log.LogInformation($"Documents modified: {documents.Count}");
log.LogInformation($"First document Id: {documents[0].Id}");
}
}
}
}
and here is the binding information of same azure function which is used to pass the param value to function
Cosmos DB trigger binding in a function.json file
{
"type": "cosmosDBTrigger",
"name": "documents",
"direction": "in",
"leaseCollectionName": "leases",
"connectionStringSetting": "<connection-app-setting>",
"databaseName": "Tasks",
"collectionName": "Items",
"createLeaseCollectionIfNotExists": true
}
To answer your question how this is even resolved at build time/deployment time" :- To use it locally we pass the same binding information in host.json file and local.settings.json file.
That's how it bind the information internally by checking param name.
Hope it helps.
Related
Could someone please help with how to enable / develop custom action with latest preview / 2.0 version of Bot Framweork. The microsoft documentation only seems to work for v1.4.1
Thanks
So, BotComponents are the new route for custom actions. Please follow the directions here. The two things you will likely have to change are:
Update/add a newer package for Microsoft.Azure.KeyVault.Core. I went with 3.0.5 for both projects.
Use "components":[{"name":"MultiplyDialog"}] instead of "components":[{"name":"CustomAction.MultiplyDialog"}].
On point #2, I was getting a build error (FileNotFoundException: Could not load file or assembly 'CustomAction.MultiplyDialog) and thefore did the above to resolve. Odd thing here is that once I was able to build in VS, then run and test in Composer, it's once again back to CustomAction.MultiplyDialog, but it works.
This documenation should make it's way to the Composer documentation once 2.0 is released.
Please find the documentation here.
Add a new project named MultiplyDialog to your solution. In Visual
Studio right-click on the solution in the Solution Explorer and
select Add > New Project. Use the Class Library project template.
Add a reference to the Microsoft.Bot.Builder.Adaptive.Runtime
package. Use the same version as the bot depends on.
Add a project reference from the bot project to the component
project. Right-click on the project and select Add > Project
Reference. Choose the MultiplyDialog project and click OK.
Build the entire solution to restore all packages and validate the
dependency tree.
Create the custom action
Actions in Composer are special implementations of the Dialog base class. This allows each action in the trigger to be pushed onto the dialog stack, and executed in turn.
In the new project, rename the Class1.cs file to MultiplyDialog.cs, and update it's contents to look like the below:
using System;
using System.Runtime.CompilerServices;
using System.Threading;
using System.Threading.Tasks;
using AdaptiveExpressions.Properties;
using Microsoft.Bot.Builder.Dialogs;
using Newtonsoft.Json;
public class MultiplyDialog : Dialog
{
[JsonConstructor]
public MultiplyDialog([CallerFilePath] string sourceFilePath = "", [CallerLineNumber] int sourceLineNumber = 0)
: base()
{
// enable instances of this command as debug break point
RegisterSourceLocation(sourceFilePath, sourceLineNumber);
}
[JsonProperty("$kind")]
public const string Kind = "MultiplyDialog";
[JsonProperty("arg1")]
public NumberExpression Arg1 { get; set; }
[JsonProperty("arg2")]
public NumberExpression Arg2 { get; set; }
[JsonProperty("resultProperty")]
public StringExpression ResultProperty { get; set; }
public override Task<DialogTurnResult> BeginDialogAsync(DialogContext dc, object options = null, CancellationToken cancellationToken = default(CancellationToken))
{
var arg1 = Arg1.GetValue(dc.State);
var arg2 = Arg2.GetValue(dc.State);
var result = Convert.ToInt32(arg1) * Convert.ToInt32(arg2);
if (this.ResultProperty != null)
{
dc.State.SetValue(this.ResultProperty.GetValue(dc.State), result);
}
return dc.EndDialogAsync(result: result, cancellationToken: cancellationToken);
}
}
Create the schema file
The .schema file for the component is a partial schema that will be merged into the main .schema file for the bot. Although it is possible to edit the main sdk.schema file for the bot directly, doing so is not recommended. Merging partial schema files will isolate changes, allow for easier recovery from errors, and enable easier packaging of your component for reuse.
Create a new file in the project named MultiplyDialog.schema and update the contents to the below:
{
"$schema": "https://schemas.botframework.com/schemas/component/v1.0/component.schema",
"$role": "implements(Microsoft.IDialog)",
"title": "Multiply",
"description": "This will return the result of arg1*arg2",
"type": "object",
"additionalProperties": false,
"properties": {
"arg1": {
"$ref": "schema:#/definitions/integerExpression",
"title": "Arg1",
"description": "Value from callers memory to use as arg 1"
},
"arg2": {
"$ref": "schema:#/definitions/integerExpression",
"title": "Arg2",
"description": "Value from callers memory to use as arg 2"
},
"resultProperty": {
"$ref": "schema:#/definitions/stringExpression",
"title": "Result",
"description": "Value from callers memory to store the result"
}
}
}
Create the BotComponent class
The adaptive runtime will dynamically discover and inject components at startup time.
Create a new MultiplyDialogBotComponent.cs file in the project and update the contents to
using Microsoft.Bot.Builder;
using Microsoft.Bot.Builder.Dialogs.Declarative;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
public class MultiplyDialogBotComponent : BotComponent
{
public override void ConfigureServices(IServiceCollection services, IConfiguration configuration)
{
// Anything that could be done in Startup.ConfigureServices can be done here.
// In this case, the MultiplyDialog needs to be added as a new DeclarativeType.
services.AddSingleton<DeclarativeType>(sp => new DeclarativeType<MultiplyDialog>(MultiplyDialog.Kind));
}
}
In the appsettings.json file of the bot project (located at \settings)to include the MultiplyDialogBotComponent in the runtimeSettings/components array.
"runtimeSettings": {
"components": [
{
"name": "MultiplyDialog"
}
]
}
Merge schema files
The final step is to merge the partial schema file from the MultiplyDialog project into the main sdk.schema file for the bot. This makes the custom action available for use in Composer.
Navigate to the schemas folder in the myBot project. This folder contains a PowerShell script and a bash script. Use an elevated PowerShell terminal to execute the PowerShell script. You will need to either copy/paste the contents of the script, or ensure your execution-policy allows for running unsigned scripts.
To validate the script executed successfully, search for MultiplyDialog inside the MyBot\schemas\sdk.schema file and validate that the partial schema from the MultiplyDialog.schema file is included in sdk.schema.
I'm assuming this is a pretty common question, how do we easily add Server info to an EventFlow event?
My scenario is that I'm deploying an application that will have its own environment specific EventFlowConfig.json, but each server in the farm will get that same json file. So... how can I tell which server in the farm sent the event to ElasticSearch?
One option is to use .net to get servername and send it as a column, which would require that I add server name to every event. That seems a little excessive but it would do the job. I was hoping there was an easier way besides having to actually code this into an event.
Thanks for your time,
Greg
Edit 4 - Karol has been great helping me get this working example up and running, THANK YOU KAROL!!! Attempting to add create a custom filter as an extension:
We need to create a new class for the custom Filter Factory
We then need to create a second new class and have it implement the IFilter interface. To pass the health monitor from the factory we used a constructor.
Use the Evaluate function as our area to add the data (eventData.AddPayloadProperty)
Then refer to the custom filter in the extensions area of our EventFlowConfig.json.
a. The category is filterFactory
b. The type is the name of your class.
c. The qualified type name is in the "type-name, assembly-name”. For example (assuming you name your filter factory ‘MyCustomFilterFactory’): “My.Application.Logging.MyCustomFilterFactory, My.Application.Assembly.WhereCustomFilterAndItsFactoreLive”
Add a reference to Microsoft.Extensions.Configuration where the C# code lives.
Then you can reference your custom filter anywhere you need to, here we are using a global filter
Working example:
class CustomGlobalFilter : IFilter
{
private IHealthReporter HealthReporter;
private string MachineName;
public CustomGlobalFilter(string ServerName, IHealthReporter HealthReporter)
{
MachineName = ServerName;
this.HealthReporter = HealthReporter;
}
FilterResult IFilter.Evaluate(EventData eventData)
{
eventData.AddPayloadProperty("ServerName", MachineName, HealthReporter, "CustomGlobalFilter");
return FilterResult.KeepEvent;
}
}
class CustomGlobalFilterFactory : IPipelineItemFactory<CustomGlobalFilter>
{
public CustomGlobalFilter CreateItem(IConfiguration configuration, IHealthReporter healthReporter)
{
CustomGlobalFilter GlobalFilter = new CustomGlobalFilter(System.Environment.MachineName, healthReporter);
return GlobalFilter;
}
}
Then in the EventFlow Config:
"filters": [
{
"type": "drop",
"include": "Level == Verbose"
},
{
"type": "CustomGlobalFilter"
}
],
...
"extensions": [
{
"category": "filterFactory",
"type": "CustomGlobalFilter",
"qualifiedTypeName": "My.Company.Presentation.App.CustomGlobalFilter, My.Company.Presentation.App"
}
It is not something that is built into EventFlow today, but there are at least a couple of options:
Use EventFlow extensibility to add a custom filter that adds these properties to every event it “sees”.
In many logging libraries there is a concept of “initializers” or “enrichment” that can be used to automatically add contextual properties. For example in Serilog (which is natively supported by EventFlow)
I'm using MVCSiteMapProvider v4.6.22 and have a dynamic node provider for one of my controllers.
Something like:
public class ProviderDetailsNodeProvider : DynamicNodeProviderBase
{
public override IEnumerable<DynamicNode> GetDynamicNodeCollection(ISiteMapNode node)
{
foreach (var provider in providers)
{
var dn = new DynamicNode()
{
Title = provider.Name,
ParentKey = "ParentKey",
Key = $"provider_master_{provider.ID}",
CanonicalUrl = "/url/something"
};
dn.RouteValues.Add("myRouteParamName", "myRouteParamValue");
yield return dn;
}
}
}
Without setting the CanonicalKey or CanonicalUrl properties of the DynamicNode, I get the correct behaviour. However I now wish to have multiple URLs pointing at the same content so I need to utilise the Canonical URL features of MVCSiteMapProvider.
If I attempt to set the CanonicalUrl as in the above snippet, or the CanonicalKey (my preferred choice), then when I attempt to use the helper methods, such as:
#Html.MvcSiteMap().SiteMapPath()
I get a NullReferenceException - it's the #Html.MvcSiteMap() which returns null.
What am I doing incorrectly, why do I get this NullReferenceException just by setting these properties against my dynamic nodes?
I'm using the MvcSiteMapProvider.MVC5 package, in an MVC6 application. I can't see a newer version on Nuget.
MVC 6 is not yet supported, as per the issue on NuGet.
I'm getting an error when trying to run the EF 4.3.1 add-migrations command:
"The model backing the ... context has changed since the database was created".
Here's one sequence that gets the error (although I've tried probably a dozen variants which also all fail)...
1) Start with a database that was created by EF Code First (ie, already contains a _MigrationHistory table with only the InitialCreate row).
2) The app's code data model and database are in-sync at this point (the database was created by CF when the app was started).
3) Because I have four DBContexts in my "Services" project, I didn't run 'enable-migrations' command (it doesn't handle multipe contexts). Instead, I manually created the Migrations folder in the Services project and the Configuration.cs file (included at end of this post). [I think I read this in a post somewhere]
4) With the database not yet changed, and the app stopped, I use the VS EDM editor to make a trivial change to my data model (add one property to an existing entity), and have it generate the new classes (but not modify the database, obviously). I then rebuild the solution and all looks OK (but don't delete the database or restart the app, of course).
5) I run the following PMC command (where "App" is the name of one of the classes in Configuration.cs):
PM> add-migration App_AddTrivial -conf App -project Services -startup Services -verbose
... which fails with the "The model ... has changed. Consider using Code First Migrations..." error.
What am I doing wrong? And does anyone else see the irony in the tool telling me to use what I'm already trying to use ;-)
What are the correct steps for setting-up a solution starting with a database that was created by EF CF? I've seen posts saying to run an initial migration with -ignorechanges, but I've tried that and it doesn't help. Actually, I've spent all DAY testing various permutations, and nothing works!
I must be doing something really stupid, but I don't know what!
Thanks,
DadCat
Configuration.cs:
namespace mynamespace
{
internal sealed class App : DbMigrationsConfiguration
{
public App()
{
AutomaticMigrationsEnabled = false;
MigrationsNamespace = "Services.App.Repository.Migrations";
}
protected override void Seed(.Services.App.Repository.ModelContainer context)
{
}
}
internal sealed class Catalog : DbMigrationsConfiguration<Services.Catalog.Repository.ModelContainer>
{
public Catalog()
{
AutomaticMigrationsEnabled = false;
MigrationsNamespace = "Services.Catalog.Repository.Migrations";
}
protected override void Seed(Services.Catalog.Repository.ModelContainer context)
{
}
}
internal sealed class Portfolio : DbMigrationsConfiguration<Services.PortfolioManagement.Repository.ModelContainer>
{
public Portfolio()
{
AutomaticMigrationsEnabled = false;
MigrationsNamespace = "Services.PortfolioManagement.Repository.Migrations";
}
protected override void Seed(Services.PortfolioManagement.Repository.ModelContainer context)
{
}
}
internal sealed class Scheduler : DbMigrationsConfiguration<.Services.Scheduler.Repository.ModelContainer>
{
public Scheduler()
{
AutomaticMigrationsEnabled = false;
MigrationsNamespace = "Services.Scheduler.Repository.Migrations";
}
protected override void Seed(Services.Scheduler.Repository.ModelContainer context)
{
}
}
}
When using EF Migrations you should have one data context per database. I know that it can grow really large, but by trying to split it you will run into several problems. One is the migration issue that you are experiencing. Later on you will probably be facing problems when trying to make queries joining tables from the different contexts. Don't go that way, it's against how EF is designed.
I need to store and retrieve lists in PhoneApplicationService.Current.State[] but this is not a list of strings or integers:
public class searchResults
{
public string title { get; set; }
public string description { get; set; }
}
public List<searchResults> resultData = new List<searchResults>()
{
//
};
The values of the result are fetched from internet and when the application is switched this data needs to be saved in isolated storage for multitasking. How do I save this list and retrieve it again?
If the question really is about how to save the data then you just do
PhoneApplicationService.Current.State["SearchResultList"] = resultData;
and to retrieve again you do
List<searchResults> loadedResultData = (List<searchResults>)PhoneApplicationService.Current.State["SearchResultList"];
Here is a complete working sample:
// your list for results
List<searchResults> resultData = new List<searchResults>();
// add some example data to save
resultData.Add(new searchResults() { description = "A description", title = "A title" });
resultData.Add(new searchResults() { description = "Another description", title = "Another title" });
// save list of search results to app state
PhoneApplicationService.Current.State["SearchResultList"] = resultData;
// --------------------->
// your app could now be tombstoned
// <---------------------
// load from app state
List<searchResults> loadedResultData = (List<searchResults>)PhoneApplicationService.Current.State["SearchResultList"];
// check if loading from app state succeeded
foreach (searchResults result in loadedResultData)
{
System.Diagnostics.Debug.WriteLine(result.title);
}
(This might stop working when your data structure gets more complex or contains certain types.)
Sounds like you just want to employ standard serialisation for your list object, see here in the MSDN docs
http://msdn.microsoft.com/en-us/library/ms973893.aspx
Or also XML serialisation if you want something that can be edited outside of the application (you can also use the Isolated Storage exploter to grab the file off and edit later)
http://msdn.microsoft.com/en-us/library/182eeyhh(v=vs.71).aspx
Alternatively i would also suggest trying out the Tombstone Helper project by Matt Lacey which can simplify this for you greatly
http://tombstonehelper.codeplex.com/
The answer by Heinrich already summarizes the main idea here - you can use the PhoneApplicationService.State with Lists like with any objects. Check out the MSDN docs on preserving application state: How to: Preserve and Restore Application State for Windows Phone. There's one important point to notice there:
Any data that you store in the State dictionary must be serializable,
either directly or by using data contracts.
Directly here means that the classes are marked as [Serializable]. Regarding your List<searchResults>, it is serializable if searchResults is serializable. To do this, either searchResults and all types referenced by it must be marked with the [Serializable] OR it must be a suitable Data Contract, see Using Data Contracts and Serializable Types. In short, make sure the class is declared as public and that it has a public, parameterless constructor.