How do I configure a TimerTrigger in Azure Webjob V3? - azure-webjobssdk

I've just updated a webjob to version 3 of the azure-sdk. The job has a TimerTrigger and as an alternative to create an appsettings.json file with the connectionstring I was hoping to use the TimerOptions class to set the ConnectionString but to my surprise the class is empty?!.
I stumbled across this https://github.com/Azure/azure-webjobs-sdk/issues/2178

I found a workaround to the above scenario regarding the TimerTrigger. In the ConfigureHostConfiguration we can use the AddInMemoryCollection to set the connectionString "AzureWebJobsStorage".
Dictionary<string, string> connectionStrings =
new Dictionary<string, string>
{
{ "AzureWebJobsStorage", ConfigurationManager.ConnectionStrings["AzureWebJobsStorage"].ConnectionString},
};
builder.ConfigureHostConfiguration(config =>
{
config.AddInMemoryCollection(connectionStrings);
});
Sadly this will not work on ServiceBusTriggers. But with ServiceBusTriggers we have an ServiceBusOptions that is not empty and can therefore be used.
builder.ConfigureWebJobs(b =>
{
b.AddAzureStorageCoreServices();
b.AddServiceBus(options =>
{
options.MessageHandlerOptions.AutoComplete = false;
options.ConnectionString =
ConfigurationManager.ConnectionStrings["AzureWebJobsServiceBus"].ConnectionString;
});
b.AddTimers();
});

Related

There is a better approach to make autofac register which database my service will use?

PLEASE READ THE EDIT SECTION, IT CAN HELP ME TO CLARIFY THE QUESTION
I have this structure right now:
WebApp.csproj
Application.csproj
Data.csproj
Oracle.csproj
SqlServer.csproj
My Data (3) is just a project referenced by WebApp (1) to decides which one (4 or 5) should be called based on the web.config.
If the web.config contains the app.key DBaseDL with 'oracle' value, it should LoadAssembly 4, instead, assembly 5.
Classes of 4 and 5 are mirror but with the query on each syntax (4 has syntax for oracle and 5 for sql server). Those mirror classes implements a commom interface between then, like this:
namespace MyProject.Oracle
{
public class User : IUser
{
//...
}
}
namespace MyProject.SqlServer
{
public class User : IUser
{
//...
}
}
On Data (3) csproj I'm trying to create a Factory which creates the User class from 4 or 5, based on the web.config settings like I said before. So I do something like this:
public class UserDataFactory : IDataLayerFactory<IUser>
{
private readonly string _key;
public UserDataFactory(string key)
{
_key = key;
}
public IUser Create()
{
string strPath = string.Empty;
string strClassName = string.Empty;
string strAssemblyName = string.Empty;
string strVersion = Assembly.GetExecutingAssembly().GetName().Version.ToString();
if (_key.Equals("oracle"))
strAssemblyName = "MyProjejct.Oracle";
else
strAssemblyName = "MyProject.SQLServer";
strPath = strAssemblyName + ", Version=" + strVersion + ", Culture=neutral, PublicKeyToken=xxxxxxx";
strClassName = strAssemblyName + ".User";
return (IUser)Assembly.Load(strPath).CreateInstance(strClassName);
}
}
My problem starts on this IDataFactory class.
I already did everything works with forced values (like always on SQLServer or Oracle) but not with IoC + the settings key for database.
I'm trying to do something like this with AutoFac but I'm probably missing something and I'm blind.
public static void ConfigureContainer()
{
var builder = new ContainerBuilder();
builder.RegisterControllers(Assembly.GetExecutingAssembly()).InstancePerRequest();
builder.RegisterType<Mediator>().As<IMediator>().InstancePerLifetimeScope();
builder.RegisterType<AutofacValidatorFactory>().As<IValidatorFactory>().SingleInstance();
builder.RegisterType<AutofacDataLayerFactory>().As<IDataFactory>().InstancePerLifetimeScope();
builder.RegisterType<FluentValidationModelValidatorProvider>().As<ModelValidatorProvider>();
builder.RegisterType<RegistryManagerService>().As<IRegistryManagerService>().SingleInstance().WithParameter("appName", ConfigurationManager.AppSettings["APPNAME"]);
builder.Register<ServiceFactory>(context =>
{
var c = context.Resolve<IComponentContext>();
return t => c.Resolve(t);
});
builder.RegisterAssemblyTypes(Assembly.Load("MyProj.Application"))
.Where(x => x.Name.EndsWith("Handler"))
.AsImplementedInterfaces();
builder.RegisterAssemblyTypes(Assembly.Load("MyProj.Application"))
.Where(x => x.Name.EndsWith("Validator"))
.AsImplementedInterfaces()
.InstancePerLifetimeScope();
builder.RegisterAssemblyTypes(Assembly.Load("MyProj.Application"))
.Where(x => x.Name.EndsWith("DataFactory"))
.AsImplementedInterfaces()
.InstancePerLifetimeScope();
var container = builder.Build();
DependencyResolver.SetResolver(new AutofacDependencyResolver(container));
}
}
Just to say, IDataFactory is about User now, but it will be a lot of things, and I don't want to register for every class again on autofac. I'm trying to register all my IDataFactory so the constructor of my handlers on Application.csproj should get the already created by factory classes, the DataAccess object from Oracle or SqlServer.
Can someone help me?
EDIT
After digging and learning a little more about the AutoFac, IoC and DI, I realize a easy way to register my class, but I'm pretty sure there is a better and more ellegant solution then mine.
I just read the key from configuration manager and write this code at the AutoFacConfig file:
if (key == "oracle")
builder.RegisterType<DocspiderOracleDbService>().As<IDocspiderDbService>().InstancePerRequest();
else
builder.RegisterType<DocspiderSqlServerDbService>().As<IDocspiderDbService>().InstancePerRequest();
This worked because only the service from the correct database is registered, but, I'll keep digging how to make it the right away.
If anyone have an idea, I'll be greatfull.

AWS Elastic Beanstalk environment variables in ASP.NET Core 1.0

How do I get environment variables from elastic beanstalk into an asp.net core mvc application? I have added a .ebextensions folder with app.config file in it with the following:
option_settings:
- option_name: HelloWorld
value: placeholder
- option_name: ASPNETCORE_ENVIRONMENT
value: placeholder
The .ebextensions folder is included in the publish package.
On deployment, both the variables are visible in the aws elasticbeanstalk console at Configuration > Software Configuration > Environment Variables
However, when I try to read the variables in the application, none of the below options are working:
Environment.GetEnvironmentVariable("HelloWorld") // In controller
Configuration["HelloWorld"] // In startup.cs
Any ideas on what I could be missing? Thanks.
I just implemented a slightly other solution which injects the beanstalk environment variables to the program so that you may access them by Environment.GetEnvironmentVariable():
private static void SetEbConfig()
{
var tempConfigBuilder = new ConfigurationBuilder();
tempConfigBuilder.AddJsonFile(
#"C:\Program Files\Amazon\ElasticBeanstalk\config\containerconfiguration",
optional: true,
reloadOnChange: true
);
var configuration = tempConfigBuilder.Build();
var ebEnv =
configuration.GetSection("iis:env")
.GetChildren()
.Select(pair => pair.Value.Split(new[] { '=' }, 2))
.ToDictionary(keypair => keypair[0], keypair => keypair[1]);
foreach (var keyVal in ebEnv)
{
Environment.SetEnvironmentVariable(keyVal.Key, keyVal.Value);
}
}
Simply call SetEbConfig(); before building your webhost. With this solution, also AWS SDK does read it's settings like AWS_ACCESS_KEY_ID correctly.
Had the same problem, and just received a reply from AWS support about this issue. Apparently environment variables are not properly injected into ASP.NET Core applications in elastic beanstalk.
As far as I know, they're working to fix the problem.
The workaround is to parse C:\Program Files\Amazon\ElasticBeanstalk\config\containerconfiguration into the configuration builder. This file is part of your elastic beanstalk environment and should be accessible upon deploying your project.
First add the file:
var builder = new ConfigurationBuilder()
.SetBasePath("C:\\Program Files\\Amazon\\ElasticBeanstalk\\config")
.AddJsonFile("containerconfiguration", optional: true, reloadOnChange: true);
Then access the values:
var env = Configuration.GetSection("iis:env").GetChildren();
foreach (var envKeyValue in env)
{
var splitKeyValue = envKeyValue.Value.Split('=');
var envKey = splitKeyValue[0];
var envValue = splitKeyValue[1];
if (envKey == "HelloWorld")
{
// use envValue here
}
}
Courtesy of
G.P. from
Amazon Web Services
I implemented the other answer to create a convenient workaround to load the environment properties from Elastic Beanstalk directly into your ASP.NET Core app configuration.
For ASP.NET Core 2.0 - edit your Program.cs
Note that this WebHost build was taken from the source code of WebHostBuilder.CreateDefaultBuilder()
https://github.com/aspnet/MetaPackages/blob/dev/src/Microsoft.AspNetCore/WebHost.cs
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Server.Kestrel.Core;
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
namespace NightSpotAdm
{
public class Program
{
public static void Main(string[] args)
{
BuildWebHost(args).Run();
}
public static IWebHost BuildWebHost(string[] args)
{
// TEMP CONFIG BUILDER TO GET THE VALUES IN THE ELASTIC BEANSTALK CONFIG
IConfigurationBuilder tempConfigBuilder = new ConfigurationBuilder();
tempConfigBuilder.AddJsonFile(
#"C:\Program Files\Amazon\ElasticBeanstalk\config\containerconfiguration",
optional: true,
reloadOnChange: true
);
IConfigurationRoot tempConfig = tempConfigBuilder.Build();
Dictionary<string, string> ebConfig = ElasticBeanstalk.GetConfig(tempConfig);
// START WEB HOST BUILDER
IWebHostBuilder builder = new WebHostBuilder()
.UseKestrel()
.UseContentRoot(Directory.GetCurrentDirectory());
// CHECK IF EBCONFIG HAS ENVIRONMENT KEY IN IT
// IF SO THEN CHANGE THE BUILDERS ENVIRONMENT
const string envKey = "ASPNETCORE_ENVIRONMENT";
if (ebConfig.ContainsKey(envKey))
{
string ebEnvironment = ebConfig[envKey];
builder.UseEnvironment(ebEnvironment);
}
// CONTINUE WITH WEB HOST BUILDER AS NORMAL
builder.ConfigureAppConfiguration((hostingContext, config) =>
{
IHostingEnvironment env = hostingContext.HostingEnvironment;
// ADD THE ELASTIC BEANSTALK CONFIG DICTIONARY
config.AddJsonFile(
"appsettings.json",
optional: true,
reloadOnChange: true
)
.AddJsonFile(
$"appsettings.{env.EnvironmentName}.json",
optional: true,
reloadOnChange: true
)
.AddInMemoryCollection(ebConfig);
if (env.IsDevelopment())
{
Assembly appAssembly = Assembly.Load(new AssemblyName(env.ApplicationName));
if (appAssembly != null)
{
config.AddUserSecrets(appAssembly, optional: true);
}
}
config.AddEnvironmentVariables();
if (args != null)
{
config.AddCommandLine(args);
}
})
.ConfigureLogging((hostingContext, logging) =>
{
logging.AddConfiguration(hostingContext.Configuration.GetSection("Logging"));
logging.AddConsole();
logging.AddDebug();
})
.UseIISIntegration()
.UseDefaultServiceProvider(
(context, options) => { options.ValidateScopes = context.HostingEnvironment.IsDevelopment(); })
.ConfigureServices(
services =>
{
services.AddTransient<IConfigureOptions<KestrelServerOptions>, KestrelServerOptionsSetup>();
});
return builder.UseStartup<Startup>().Build();
}
}
public static class ElasticBeanstalk
{
public static Dictionary<string, string> GetConfig(IConfiguration configuration)
{
return
configuration.GetSection("iis:env")
.GetChildren()
.Select(pair => pair.Value.Split(new[] { '=' }, 2))
.ToDictionary(keypair => keypair[0], keypair => keypair[1]);
}
}
}
For ASP.NET Core 1.0
public Startup(IHostingEnvironment env)
{
var builder = new ConfigurationBuilder()
.SetBasePath(env.ContentRootPath)
.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true)
.AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true)
.AddJsonFile(#"C:\Program Files\Amazon\ElasticBeanstalk\config\containerconfiguration", optional: true, reloadOnChange: true)
.AddEnvironmentVariables();
var config = builder.Build();
builder.AddInMemoryCollection(GetEbConfig(config));
Configuration = builder.Build();
}
private static Dictionary<string, string> GetEbConfig(IConfiguration configuration)
{
Dictionary<string, string> dict = new Dictionary<string, string>();
foreach (IConfigurationSection pair in configuration.GetSection("iis:env").GetChildren())
{
string[] keypair = pair.Value.Split(new [] {'='}, 2);
dict.Add(keypair[0], keypair[1]);
}
return dict;
}
Instead of having to parse the containerconfiguration you can leverage the ebextensions options to set the variable as part of your deploy process:
commands:
set_environment:
command: setx ASPNETCORE_ENVIRONMENT "Development" /M
This will set a global environment variable as part of your application deployment. This variable use-case is officially supported and documented by Microsoft.
After deploying your app you can verify the setting is set correctly in the EC2 instance:
AWS addressed this issue in the Elastic Beanstalk Windows Server platform update on June 29, 2020:
Previously, Elastic Beanstalk didn't support passing environment variables to .NET Core applications and multiple-application IIS deployments that use a deployment manifest [1]. The Elastic Beanstalk Windows Server platform update on June 29, 2020 [2] now fixes this gap. For details, see Configuring your .NET environment in the Elastic Beanstalk console [3].
[1] https://docs.aws.amazon.com/elasticbeanstalk/latest/dg/dotnet-manifest.html
[2] https://docs.aws.amazon.com/elasticbeanstalk/latest/relnotes/release-2020-06-29-windows.html
[3] https://docs.aws.amazon.com/elasticbeanstalk/latest/dg/create_deploy_NET.container.console.html#dotnet-console
Above solution doesnt helped me to load config file based on enviroment settings. So here is my solution AWS ElasticBeansTalk "hack"
public Startup(IHostingEnvironment env)
{
var builder = new ConfigurationBuilder()
.SetBasePath(env.ContentRootPath)
.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true)
.AddJsonFile($"appsettings.{GetEnvVariableAWSBeansTalkHack(env)}.json", optional: true)
.AddEnvironmentVariables();
Configuration = builder.Build();
}
private static string GetEnvVariableAWSBeansTalkHack(IHostingEnvironment env)
{
var config = new ConfigurationBuilder()
.AddJsonFile(#"C:\Program Files\Amazon\ElasticBeanstalk\config\containerconfiguration", optional: true, reloadOnChange: true).Build();
Dictionary<string, string> dict = new Dictionary<string, string>();
foreach (IConfigurationSection pair in config.GetSection("iis:env").GetChildren())
{
string[] keypair = pair.Value.Split(new[] { '=' }, 2);
dict.Add(keypair[0], keypair[1]);
}
return dict.ContainsKey("ASPNETCORE_ENVIRONMENT")
? dict["ASPNETCORE_ENVIRONMENT"]
: env.EnvironmentName;
}
You can create an implementation of Microsoft.Extensions.Configuration.
Also available at https://gist.github.com/skarllot/11e94ed8901a9ddabdf05c0e5c08dbc5.
using Microsoft.Extensions.Configuration;
using Newtonsoft.Json.Linq;
using System.IO;
using System.Linq;
namespace Microsoft.Extensions.Configuration.AWS
{
public class AmazonEBConfigurationProvider : ConfigurationProvider
{
private const string ConfigurationFilename = #"C:\Program Files\Amazon\ElasticBeanstalk\config\containerconfiguration";
public override void Load()
{
if (!File.Exists(ConfigurationFilename))
return;
string configJson;
try
{
configJson = File.ReadAllText(ConfigurationFilename);
}
catch
{
return;
}
var config = JObject.Parse(configJson);
var env = (JArray)config["iis"]["env"];
if (env.Count == 0)
return;
foreach (var item in env.Select(i => (string)i))
{
int eqIndex = item.IndexOf('=');
Data[item.Substring(0, eqIndex)] = item.Substring(eqIndex + 1);
}
}
}
public class AmazonEBConfigurationSource : IConfigurationSource
{
public IConfigurationProvider Build(IConfigurationBuilder builder)
{
return new AmazonEBConfigurationProvider();
}
}
public static class AmazonEBExtensions
{
public static IConfigurationBuilder AddAmazonElasticBeanstalk(this IConfigurationBuilder configurationBuilder)
{
configurationBuilder.Add(new AmazonEBConfigurationSource());
return configurationBuilder;
}
}
}
Then use with your ConfigurationBuilder:
var builder = new ConfigurationBuilder()
.SetBasePath(env.ContentRootPath)
.AddJsonFile("appsettings.json", true, true)
.AddJsonFile($"appsettings.{env.EnvironmentName}.json", true)
.AddAmazonElasticBeanstalk() // <-- Merge with other sources
.AddEnvironmentVariables();
.NET Core 2 + posrgresql RDS
Further to #sebastian's great answer above, I found that the settings were in a different part of the file, viz. plugins:rds:env.
Also there was no need to split on =, so the parsing code I have is:
private static void SetEbConfig()
{
var tempConfigBuilder = new ConfigurationBuilder();
tempConfigBuilder.AddJsonFile(
#"C:\Program Files\Amazon\ElasticBeanstalk\config\containerconfiguration",
optional: true,
reloadOnChange: true
);
var configuration = tempConfigBuilder.Build();
var ebEnv = configuration.GetSection("plugins:rds:env")
.GetChildren()
.ToDictionary(child => child.Key, child => child.Value);
foreach (var keyVal in ebEnv)
{
Environment.SetEnvironmentVariable(keyVal.Key, keyVal.Value);
}
}
The relevant (and redacted ;-)) JSON is as follows:
{
"plugins": {
"rds": {
"Description": "RDS Environment variables",
"env": {
"RDS_PORT": "....",
"RDS_HOSTNAME": "....",
"RDS_USERNAME": "....",
"RDS_DB_NAME": "....",
"RDS_PASSWORD": "...."
}
}
}
}
(This reply is separate since I don't have rep to comment...)
This can definitely be done in an .ebextensions folder. Simply create a new file in your .ebextensions folder (I used a name of "options.config"), mark it as "copy if newer" or "copy always" and make sure you use the option_settings header with a aws:elasticbeanstalk:application:environment namespace:
option_settings:
aws:elasticbeanstalk:application:environment:
MyEnvVar: SomeValue
EDIT: I forgot to include a link to the docs!
https://docs.aws.amazon.com/elasticbeanstalk/latest/dg/environments-cfg-softwaresettings.html
Update for ASP.net Core 3
Set ASPNETCORE_ENVIRONMENT environment variable in Elastic Beanstalk to Staging, Production, Development...
In Core project create appsettings.Staging.json and this configuration will be used when this the project is deployed.

Change Default Version Swagger Location

Using Swashbuckle in conjuntion with c.MultipleApiVersions((apiDesc, version) =>... the result is our swagger file resides at eg: https://host/api/swagger/docs/{version}. I would like to actually have the swagger file at https://host/api/{version}/swagger. Is it possible the I can set this up in my SwaggerConfig .EnableSwagger()?
This would allow for the following Urls :
http://host/api/v1/swagger/
http://host/api/v2/swagger/
Appreciate the help.
To do that way, you can update the swaggerconfig file as shown below:
.EnableSwagger("{apiVersion}/swagger", c =>
{
c.MultipleApiVersions(
(vc) =>
{
vc.Version("v2", "Swashbuckle Dummy API V2");
vc.Version("v1", "Swashbuckle Dummy API V1");
});
});
Just for further references in asp.net core 2.2 it will lokk like this
app.UseSwagger(options =>
{
options.RouteTemplate = "docs/{documentName}/docs.json";
});
app.UseSwaggerUI(options =>
{
//Build a swagger endpoint for each discovered API version
foreach (var description in provider.ApiVersionDescriptions.OrderByDescending(x=>x.ApiVersion).AsList())
{
options.SwaggerEndpoint($"/docs/{description.GroupName}/docs.json", description.GroupName);
}
options.RoutePrefix = "docs";
}
);
in which provider is IApiVersionDescriptionProvider injected by DI

Replace Sharp Architecture's NHibernate.config with a Fluent Configuration

By default, the solution generated from Sharp Architecture's templify package configures NHibernate using an NHibernate.config file in the {SolutionName}.Web project. I would like to replace it with a fluent configuration of my own and still have the rest of Sharp Architecture work correctly.
Any help will be much appreciated. :)
Solution: Here's how I got it to work:
IPersistenceConfigurer configurer = OracleClientConfiguration.Oracle10
.AdoNetBatchSize(500)
.ShowSql()
.ConnectionString(c => c.FromConnectionStringWithKey("NHibernate.Localhost"))
.DefaultSchema("MySchema")
.ProxyFactoryFactory("NHibernate.ByteCode.Castle.ProxyFactoryFactory, NHibernate.ByteCode.Castle")
.UseReflectionOptimizer();
NHibernateSession.Init(
webSessionStorage,
new string[] { Server.MapPath("~/bin/MyProject.Data.dll") },
new AutoPersistenceModelGenerator().Generate(),
null,
null,
null,
configurer);
iirc the NhibernateSession class that is used to configure nhibernate has a bunch of overloads one of them giving you the ability to configure it via code.
Very old post. I'll leave it here in case someone else is interested. On SharpArch 1.9.6.0 you can add two methods to NHibernateSession.cs. This will let you pass-in a FluentConfiguration object.
public static FluentConfiguration Init(ISessionStorage storage, FluentConfiguration fluentConfiguration)
{
InitStorage(storage);
try
{
return AddConfiguration(DefaultFactoryKey, fluentConfiguration);
}
catch
{
// If this NHibernate config throws an exception, null the Storage reference so
// the config can be corrected without having to restart the web application.
Storage = null;
throw;
}
}
private static FluentConfiguration AddConfiguration(string defaultFactoryKey, FluentConfiguration fluentConfiguration)
{
var sessionFactory = fluentConfiguration.BuildSessionFactory();
Check.Require(!sessionFactories.ContainsKey(defaultFactoryKey),
"A session factory has already been configured with the key of " + defaultFactoryKey);
sessionFactories.Add(defaultFactoryKey, sessionFactory);
return fluentConfiguration;
}

Setting timeout for new URL(...).text in Groovy/Grails

I use the following Groovy snippet to obtain the plain-text representation of an HTML-page in a Grails application:
String str = new URL("http://www.example.com/some/path")?.text?.decodeHTML()
Now I want to alter the code so that the request will timeout after 5 seconds (resulting instr == null). What is the easiest and most Groovy way to achieve that?
I checked source code of groovy 2.1.8, below code is available:
'http://www.google.com'.toURL().getText([connectTimeout: 2000, readTimeout: 3000])
The logic to process configuration map is located in method org.codehaus.groovy.runtime.ResourceGroovyMethods#configuredInputStream
private static InputStream configuredInputStream(Map parameters, URL url) throws IOException {
final URLConnection connection = url.openConnection();
if (parameters != null) {
if (parameters.containsKey("connectTimeout")) {
connection.setConnectTimeout(DefaultGroovyMethods.asType(parameters.get("connectTimeout"), Integer.class));
}
if (parameters.containsKey("readTimeout")) {
connection.setReadTimeout(DefaultGroovyMethods.asType(parameters.get("readTimeout"), Integer.class));
}
if (parameters.containsKey("useCaches")) {
connection.setUseCaches(DefaultGroovyMethods.asType(parameters.get("useCaches"), Boolean.class));
}
if (parameters.containsKey("allowUserInteraction")) {
connection.setAllowUserInteraction(DefaultGroovyMethods.asType(parameters.get("allowUserInteraction"), Boolean.class));
}
if (parameters.containsKey("requestProperties")) {
#SuppressWarnings("unchecked")
Map<String, String> properties = (Map<String, String>) parameters.get("requestProperties");
for (Map.Entry<String, String> entry : properties.entrySet()) {
connection.setRequestProperty(entry.getKey(), entry.getValue());
}
}
}
return connection.getInputStream();
}
You'd have to do it the old way, getting a URLConnection, setting the timeout on that object, then reading in the data through a Reader
This would be a good thing to add to Groovy though (imho), as it's something I could see myself needing at some point ;-)
Maybe suggest it as a feature request on the JIRA?
I've added it as a RFE on the Groovy JIRA
https://issues.apache.org/jira/browse/GROOVY-3921
So hopefully we'll see it in a future version of Groovy...

Resources