Unable to parse json file in SourceGenerator - json

I am attempting to read a json file and generate C# code. I have a source generator like below, and if I uncomment the JsonDocument line, the source generator stops working. In fact, if I use any class from System.Text.Json it stops working. I have also attempted to use Newtonsoft.Json same result.
Whilst building the target project that is using this source generator I get a build error error MSB6006: "csc.exe" exited with code -532462766.
using System;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Text;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Text;
using System.Text.Json;
namespace Raya.Plugin.Registrations.SourceGenerator
{
[Generator]
public class DeviceRegistrationGenerator : ISourceGenerator
{
public void Execute(GeneratorExecutionContext context)
{
var metadata = context.AdditionalFiles.Single(x => x.Path.EndsWith("plugin.metadata.json"));
// var doc = JsonDocument.Parse(metadata.GetText(context.CancellationToken).ToString());
}
public void Initialize(GeneratorInitializationContext context)
{
#if DEBUG
if (!Debugger.IsAttached)
{
Debugger.Launch();
}
#endif
Debug.WriteLine("Initalize code generator");
}
}
}
Generator csproj
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<DevelopmentDependency>true</DevelopmentDependency>
<IncludeBuildOutput>false</IncludeBuildOutput>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
<LangVersion>latest</LangVersion>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
<LangVersion>latest</LangVersion>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.4.0" PrivateAssets="all" />
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Workspaces" Version="4.4.0" PrivateAssets="all" />
<PackageReference Include="Microsoft.CodeAnalysis.Analyzers" Version="3.3.3" PrivateAssets="all" />
<PackageReference Include="System.Text.Json" Version="6.0.5" GeneratePathProperty="true" PrivateAssets="all"/>
</ItemGroup>
</Project>
UPDATE
Adding following seems to make it a work a bit, but I am not sure what is happening. Now I am getting InvalidCastException
<PropertyGroup>
<GetTargetPathDependsOn>$(GetTargetPathDependsOn);GetDependencyTargetPaths</GetTargetPathDependsOn>
<GeneratePackageOnBuild>True</GeneratePackageOnBuild>
</PropertyGroup>
<Target Name="GetDependencyTargetPaths" AfterTargets="ResolvePackageAssets">
<ItemGroup>
<TargetPathWithTargetPlatformMoniker Include="#(ResolvedCompileFileDefinitions)" IncludeRuntimeDependency="false" />
</ItemGroup>
</Target>
Exception
Since the project using this source generator also ends up referencing System.Text.Json.SourceGenerator.dll, I am getting following exception from that dll.
Unhandled Exception: System.InvalidCastException: [A]Microsoft.CodeAnalysis.CSharp.Syntax.CompilationUnitSyntax cannot be cast to [B]Microsoft.CodeAnalysis.CSharp.Syntax.CompilationUnitSyntax. Type A originates from 'Microsoft.CodeAnalysis.CSharp, Version=4.4.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35' in the context 'Default' at location 'C:\Program Files\Microsoft Visual Studio\2022\Enterprise\MSBuild\Current\Bin\Roslyn\Microsoft.CodeAnalysis.CSharp.dll'. Type B originates from 'Microsoft.CodeAnalysis.CSharp, Version=4.4.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35' in the context 'LoadFrom' at location 'C:\Users\myuser.nuget\packages\microsoft.codeanalysis.csharp\4.4.0\lib\netstandard2.0\Microsoft.CodeAnalysis.CSharp.dll'

Related

.net core 3 not having ReferenceLoopHandling in AddJsonOptions

My csproject file is indicating: <TargetFramework>netcoreapp3.0</TargetFramework>
In my startup im using the followinhg:
services.AddMvc(x => x.Filters.AddService<TransactionFilter>())
.AddJsonOptions(options => options.JsonSerializerOptions... )
But, ReferenceLoopHandling is not available inside options.JsonSerializerOptions.
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>netcoreapp3.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="FluentNHibernate" Version="2.1.2" />
<PackageReference Include="FullContact.Contacts.API" Version="1.0.3" />
<PackageReference Include="Google.Cloud.Storage.V1" Version="2.3.0" />
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="2.2.0" />
<PackageReference Include="Microsoft.AspNetCore.Mvc.Cors" Version="2.2.0" />
<PackageReference Include="Microsoft.AspNetCore.Mvc.Formatters.Json" Version="2.2.0" />
<PackageReference Include="Microsoft.IdentityModel.Tokens" Version="5.5.0" />
<PackageReference Include="MySql.Data" Version="8.0.17" />
<PackageReference Include="piplclient" Version="5.0.9" />
<PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="5.5.0" />
</ItemGroup>
</Project>
As part of the work to improve the ASP.NET Core shared framework, Json.NET has been removed from the ASP.NET Core shared framework. Your app may require this reference if it uses Newtonsoft.Json-specific feature such as JsonPatch or converters or if it formats Newtonsoft.Json-specific types.
To use Json.NET in an ASP.NET Core 3.0 project:
Add a package reference to Microsoft.AspNetCore.Mvc.NewtonsoftJson.
Update Startup.ConfigureServices to call AddNewtonsoftJson.
services.AddMvc()
.AddNewtonsoftJson();
This sets up MVC and configures it to use Json.NET instead of that new API. And that AddNewtonsoftJson method has an overload that allows you to configure the Json.NET options like you were used to with AddJsonOptions in ASP.NET Core 2.x.
services.AddMvc()
.AddNewtonsoftJson(options =>
{
options.SerializerSettings = new JsonSerializerSettings() { … };
});
Reference:
https://learn.microsoft.com/en-us/aspnet/core/migration/22-to-30?view=aspnetcore-2.2&tabs=visual-studio#jsonnet-support
https://stackoverflow.com/a/55666898/10201850
As of March 2020, the default JSON serializer does not support reference loop handling.
In order to handle that issue, you'll have to first install the older JSON serializer (used in older versions of .NET Core), Microsoft.AspNetCore.Mvc.NewtonsoftJson in the Nuget package manager.
The usage is pretty simple:
services.AddMvc().AddNewtonsoftJson(o =>
{
o.SerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore;
});
Or like this if you are using a simple web API:
services.AddControllers().AddNewtonsoftJson(o =>
{
o.SerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore;
});
As mentioned above you need to install Microsoft.AspNetCore.Mvc.NewtonsoftJson, Microsoft.AspNetCore.SignalR.Protocols.Newtonsoft packages and configure with AddNewtonsoftJsonProtocol in order to still use Newtonsoft instead of System.Text.Json (ReferenceLoopHandling not available yet)
For SignalR it would be
services.AddSignalR().AddNewtonsoftJsonProtocol(p =>
{
p.PayloadSerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore;
});
Add a package Microsoft.AspNetCore.Mvc.NewtonsoftJson
version - 3.1.3
services.AddMvc().AddNewtonsoftJson(options => options.SerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore);
MvcNewtonsoftJsonOptions
services.PostConfigure<MvcNewtonsoftJsonOptions>(o =>
{
o.SerializerSettings.ContractResolver = new MyCustomContractResolver()
{
NamingStrategy = new CamelCaseNamingStrategy()
};
o.SerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore;
});

BIML SSIS ScriptTask as a data source - Error with OutputBuffer

I am getting the below error when trying to generate a package through BIML using a ScriptTask as a datasource. I have a large (circa 5GB) XML file to load and wanted to use a StreamReader to get the data into the database.
'Output0Buffer' does not contain a definition for 'PORTF_LIST' and no extension method 'PORTF_LIST' accepting a first argument of type 'Output0Buffer' could be found (are you missing a using directive or an assembly reference?).
This is occurring for each column. The columns are dynamic and come from a separate method in a c# class looking at the dacpac so should be the same names and casing everywhere.
Sample of the file as below:
<ANALYTICS>
<INSTRUMENTS ASOF_DATE="3/31/2017" CREATE_DATE="4/2/2017" RECORDS="3763">
<INSTRUMENT>
<PORTF_LIST>XX1245897</PORTF_LIST>
<PRT_FULL_NAME>Convertible Bonds</PRT_FULL_NAME>
<ISIN>11803384</ISIN>
</INSTRUMENT>
</INSTRUMENTS>
</ANALYTICS>
Output buffer is defined as below (there are 250 odd columns, but all follow the same pattern:
<OutputBuffers>
<OutputBuffer Name="Output0" IsSynchronous="false">
<Columns>
<Column Name="PORTF_LIST" DataType="String" Length="255"/>
<Column Name="PRT_FULL_NAME" DataType="String" Length="255"/>
<Column Name="ISIN" DataType="String" Length="255"/>
</Columns>
</OutputBuffer>
</OutputBuffers>
The script task code where I am trying to add to the buffer is below:
<## property name="Elements" type="String" #>
<## property name="Columns" type="String" #>
<## property name="BufferColumns" type="String" #>
<## property name="RootElement" type="String" #>
<ScriptComponentProject ProjectCoreName="SC_eb1debcd2374468ebccbbfad4fbe5976" Name="XmlSource">
<AssemblyReferences>
<AssemblyReference AssemblyPath="Microsoft.SqlServer.DTSPipelineWrap" />
<AssemblyReference AssemblyPath="Microsoft.SqlServer.DTSRuntimeWrap" />
<AssemblyReference AssemblyPath="Microsoft.SqlServer.PipelineHost" />
<AssemblyReference AssemblyPath="Microsoft.SqlServer.TxScript" />
<AssemblyReference AssemblyPath="Microsoft.SqlServer.ManagedDTS.dll" />
<AssemblyReference AssemblyPath="Microsoft.SqlServer.ScriptTask.dll" />
<AssemblyReference AssemblyPath="System.dll" />
<AssemblyReference AssemblyPath="System.AddIn.dll" />
<AssemblyReference AssemblyPath="System.Data.dll" />
<AssemblyReference AssemblyPath="System.Windows.Forms.dll" />
<AssemblyReference AssemblyPath="System.Xml.dll" />
<AssemblyReference AssemblyPath="System.Xml.Linq.dll" />
<AssemblyReference AssemblyPath="System.Core.dll" />
</AssemblyReferences>
<OutputBuffers>
<!--
Define what your buffer is called and what it looks like
Must set IsSynchronous as false. Otherwise it is a transformation
(one row enters, one row leaves) and not a source.
-->
<OutputBuffer Name="Output0" IsSynchronous="false">
<Columns>
<#=BufferColumns#>
</Columns>
</OutputBuffer>
</OutputBuffers>
<Files>
<File Path="Properties\AssemblyInfo.cs">
using System.Reflection;
using System.Runtime.CompilerServices;
[assembly: AssemblyTitle("XmlSource")]
[assembly: AssemblyDescription("Script Component as source")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("XmlSource")]
[assembly: AssemblyCopyright("Copyright # 2017")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
[assembly: AssemblyVersion("1.0.*")]
</File>
<File Path="main.cs">
<![CDATA[
using System;
using System.Data;
using Microsoft.SqlServer.Dts.Pipeline.Wrapper;
using Microsoft.SqlServer.Dts.Runtime.Wrapper;
using System.Security;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Xml;
using System.Xml.Linq;
using System.Windows.Forms;
[Microsoft.SqlServer.Dts.Pipeline.SSISScriptComponentEntryPointAttribute]
public class ScriptMain : UserComponent
{
public override void PreExecute()
{
base.PreExecute();
}
public override void PostExecute()
{
base.PostExecute();
}
public string sourceFile = Dts.Variables["User::FileName"].Value.ToString();
public override void CreateNewOutputRows()
{
foreach (var myXmlData in (
from elements in StreamReader(sourceFile, "INSTRUMENT")
select new
{
PORTF_LIST = elements.Element("PORTF_LIST").Value,
PRT_FULL_NAME = elements.Element("PRT_FULL_NAME").Value,
ISIN = elements.Element("ISIN").Value
}
))
{
try
{
Output0Buffer.AddRow();
Output0Buffer.PORTF_LIST = myXmlData.PORTF_LIST;
Output0Buffer.PRT_FULL_NAME = myXmlData.PRT_FULL_NAME;
Output0Buffer.ISIN = myXmlData.ISIN;
}
catch (Exception e)
{
string errorMessage = string.Format("Data retrieval failed: {0}", e.Message);
bool cancel;
ComponentMetaData.FireError(0, ComponentMetaData.Name, errorMessage,string.Empty,0, out cancel);
}
}
}
public static IEnumerable<XElement> StreamReader(String filename, string elementName)
{
// Create an XML reader for this file.
using (XmlReader reader = XmlReader.Create(filename))
{
reader.MoveToContent(); // will not advance reader if already on a content node; if successful, ReadState is Interactive
reader.Read(); // this is needed, even with MoveToContent and ReadState.Interactive
while(!reader.EOF && reader.ReadState == ReadState.Interactive)
{
if(reader.NodeType == XmlNodeType.Element && reader.Name.Equals(elementName))
{
// this advances the reader...so it's either XNode.ReadFrom() or reader.Read(), but not both
var matchedElement = XNode.ReadFrom(reader) as XElement;
if(matchedElement != null)
yield return matchedElement;
}
else
reader.Read();
}
reader.Close();
}
}
}
]]>
</File>
</Files>
<ReadOnlyVariables>
<Variable Namespace="User" DataType="String" VariableName="FileName" />
</ReadOnlyVariables>
<ReadWriteVariables>
</ReadWriteVariables>
</ScriptComponentProject>
I've checked the code in a console app and it reads the XML file fine, but no luck with the BIML. There are about 250 odd columns so I am trying to avoid doing this manually so if you have any ideas what I am doing wrong I'd really appreciate it!
It seems that the script task does not like underscores in the OutputBuffer.
I created a stub package manually and intellisense had PORTFLIST rather than PORTF_LIST when assigning the value.
So that snippit of code should be:
Output0Buffer.AddRow();
Output0Buffer.PORTFLIST = myXmlData.PORTF_LIST;
Output0Buffer.PRTFULLNAME = myXmlData.PRT_FULL_NAME;
Output0Buffer.ISIN = myXmlData.ISIN
I have another error, my favorite "EmitSsis. Internal Compiler Error: Workflow EmitSsis contains fatal errors.", but at least this one is solved!
Thanks Bill for your help, and sorry I led you down the garden path with the wrong column name in the posted error, or you probably would have known the issue!

Error CS1061 'DbContextOptionsBuilder' does not contain a definition for 'UseMySql'

I just learn to use ASP.NET CORE 2.0 MVC using Visual Studio Community Edition.
I want to use MySQL database instead of using SQL Server because I need to use some data inside the old MySQL DB. Please help me solving this problem .. thank you
Here is my error:
Severity Code Description Project File Line Suppression State
Error CS1061 'DbContextOptionsBuilder' does not contain a definition
for 'UseMySql' and no extension method 'UseMySql' accepting a first
argument of type 'DbContextOptionsBuilder' could be found (are you
missing a using directive or an assembly
reference?) LearnEFCore d:\temp\aspnet\LearnEFCore\LearnEFCore\Startup.cs 29 Active
My code as follow:
In Startup.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.EntityFrameworkCore;
using LearnEFCore.Data;
....
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
var sqlConnectionString = Configuration.GetConnectionString("DataAccessMySqlProvider");
services.AddDbContext<SchoolContext>(options => options.UseMySql(sqlConnectionString));
services.AddMvc();
}
In my appsettings.json
{
"Logging": {
"IncludeScopes": false,
"LogLevel": {
"Default": "Warning"
}
},
"ConnectionStrings": {
"DataAccessMySqlProvider": "server=localhost;port=3306;userid=root;password=root;database=sportstore;"
}
}
In my Models/Data
using LearnEFCore.Models;
using Microsoft.EntityFrameworkCore;
namespace LearnEFCore.Data
{
public class SchoolContext : DbContext
{
public SchoolContext(DbContextOptions<SchoolContext> options) : base(options)
{
}
public DbSet<Course> Courses { get; set; }
public DbSet<Enrollment> Enrollments { get; set; }
public DbSet<Student> Students { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Course>().ToTable("Course");
modelBuilder.Entity<Enrollment>().ToTable("Enrollment");
modelBuilder.Entity<Student>().ToTable("Student");
}
}
}
My csproj
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>netcoreapp2.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.All" Version="2.0.0" />
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="2.0.0" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="2.0.0" />
<PackageReference Include="MySql.Data.EntityFrameworkCore" Version="6.10.4" />
</ItemGroup>
<ItemGroup>
<DotNetCliToolReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Tools" Version="2.0.0" />
</ItemGroup>
</Project>
changin to Pomelo.EntityFrameworkCore.MySql solved the problem ...

How do you access the Package Variables Collection in biml?

I'm just getting started with biml and bimlscript. I can see the power it holds, but so far digging through the language and API reference has been frustrating. I can't seem to find any reference online to access the package's variable collection.
I'm trying to set up this script so I can add more variables into my Variables section, and then automatically add those variables to a script task later in the process.
Here is the minimal code for my problem:
<Biml xmlns="http://schemas.varigence.com/biml.xsd" >
<Packages>
<Package Name="Load">
<Variables>
<Variable Name="ETLProcessStepID" DataType="Int32">0</Variable>
<Variable Name="TenantID" DataType="Int32">1</Variable>
</Variables>
<!-- more stuff going on in the biml -->
<# var package = RootNode.Packages.Where(loadPackage => loadPackage.Name.Contains("Load"));
foreach (var variable in package.Variables) { #>
<ReadWriteVariables VariableName="<#=variable.Name#>" />
<# }#>
</Package>
</Packages>
</Biml>
This seems to be the closest I've gotten. Unfortunately It results in:
Error 0 'System.Collections.Generic.IEnumerable<Varigence.Languages.Biml.Task.AstPackageNode>' does not contain a definition for 'Variables' and no extension method 'Variables' accepting a first argument of type 'System.Collections.Generic.IEnumerable<Varigence.Languages.Biml.Task.AstPackageNode>' could be found (are you missing a using directive or an assembly reference?).
If I'm reading the documentation right, there is a Variables collection in the Packages node. https://varigence.com/Documentation/Api/Type/AstPackageNode
If I'm not reading the documentation right, can anyone direct me to a reference on how I could access the package variables collection?
The first error you're running into is that your C# variable called package is going to return a collection from that Linq call. Since there should only be one element that matches it, we'll use First to just give us one of these things
var package = RootNode.Packages.Where(loadPackage => loadPackage.Name.Contains("Load")).First();
Now the tricky part and I'll actually have to check with some bigger guns on this, but I don't think you'd be able to access the current package's variable collection like that because it's not built yet. Well, at least using BIDS Helper/BimlExpress. The Biml first needs to get compiled into objects because, assuming a single select, you won't have anything in the RootNode.Packages collection. You certainly wouldn't have "Load" because you're compiling it right now.
In Mist, the paid for solution which is soon to be rebranded as BimlStudio, you could use a Transformer to accomplish this. You'd build out the Load package first and then a transformer fires off just prior to emission as a dtsx package and does whatever correction you were trying.
Consider the following test harness. It creates a simple package and then has some bimlscript immediately after it wherein I enumerate though all the packages and then for each package, I enumerate the root level Variables collection. You'll only see the "Test" message rendered. The inner calls won't fire because nothing exists yet.
<Biml xmlns="http://schemas.varigence.com/biml.xsd">
<Packages>
<Package Name="so_38908470" >
<Variables>
<Variable Name="ETLProcessStepID" DataType="Int32">0</Variable>
<Variable Name="TenantID" DataType="Int32">1</Variable>
</Variables>
<#
string message = "<!-- {0} -->";
WriteLine(message, "test");
foreach (var package in RootNode.Packages)
{
WriteLine(message, package.Name);
foreach(var variable in package.Variables)
{
WriteLine(message, variable.Name);
}
}
#>
</Package>
</Packages>
</Biml>
The more I think about this, Tiering might be able to accomplish this with BIDS Helper/BimlExpress. Since it looks like you're trying to use the Variables defined within a package as inputs to a Script Task or Component, as long as you're using ScriptProjects type things which are parallel to a Packages collection, this might work.
Eureka
Add two Biml files to your project: Load.biml and Script.Biml. Use the following code in each. Select both and right click to generate SSIS package.
Load.biml
This is going to be your package. It is the package you started up above with a Script Task in there that is going to dump the name and value of all the user variables declared at the root of the package. But as you see, there isn't anything in the ScriptTask tag that specifies what variables or what the code is going to do.
<Biml xmlns="http://schemas.varigence.com/biml.xsd" >
<Packages>
<Package Name="Load">
<Variables>
<Variable Name="ETLProcessStepID" DataType="Int32">0</Variable>
<Variable Name="TenantID" DataType="Int32">1</Variable>
</Variables>
<Tasks>
<Script ProjectCoreName="ST_EchoBack" Name="SCR Echo Back">
<ScriptTaskProjectReference ScriptTaskProjectName="ST_EchoBack" />
</Script>
</Tasks>
</Package>
</Packages>
</Biml>
Script.biml
This biml looks like a lot but it's the same concepts as I was working with above where I enumerate though the packages collection and then work with the Variables collection. I use the biml nuggets to control the emission of the Namespace, Name and DataType properties.
<## template language="C#" tier="1" #>
<Biml xmlns="http://schemas.varigence.com/biml.xsd" >
<ScriptProjects>
<ScriptTaskProject ProjectCoreName="ST_EchoBack" Name="ST_EchoBack" VstaMajorVersion="0">
<ReadOnlyVariables>
<!-- List all the variables you are interested in tracking -->
<#
string message = "<!-- {0} -->";
WriteLine(message, "test");
// ValidationReporter.Report(Severity.Error, "test");
foreach (var package in RootNode.Packages.Where(x=> x.Name == "Load"))
{
WriteLine(message, package.Name);
// ValidationReporter.Report(Severity.Error, package.Name);
foreach(var variable in package.Variables)
{
WriteLine(message, variable.Name);
// ValidationReporter.Report(Severity.Error, variable.Name);
#>
<Variable Namespace="<#=variable.Namespace#>" VariableName="<#=variable.Name#>" DataType="<#=variable.DataType#>" />
<#
}
}
#>
</ReadOnlyVariables>
<Files>
<File Path="ScriptMain.cs" BuildAction="Compile">using System;
using System.Data;
using Microsoft.SqlServer.Dts.Runtime;
using System.Windows.Forms;
namespace ST_EchoBack
{
[Microsoft.SqlServer.Dts.Tasks.ScriptTask.SSISScriptTaskEntryPointAttribute]
public partial class ScriptMain : Microsoft.SqlServer.Dts.Tasks.ScriptTask.VSTARTScriptObjectModelBase
{
public void Main()
{
bool fireAgain = false;
string message = "{0}::{1} : {2}";
foreach (var item in Dts.Variables)
{
Dts.Events.FireInformation(0, "SCR Echo Back", string.Format(message, item.Namespace, item.Name, item.Value), string.Empty, 0, ref fireAgain);
}
Dts.TaskResult = (int)ScriptResults.Success;
}
enum ScriptResults
{
Success = Microsoft.SqlServer.Dts.Runtime.DTSExecResult.Success,
Failure = Microsoft.SqlServer.Dts.Runtime.DTSExecResult.Failure
};
}
} </File>
<File Path="Properties\AssemblyInfo.cs" BuildAction="Compile">
using System.Reflection;
using System.Runtime.CompilerServices;
[assembly: AssemblyVersion("1.0.*")]
</File>
</Files>
<AssemblyReferences>
<AssemblyReference AssemblyPath="System" />
<AssemblyReference AssemblyPath="System.Data" />
<AssemblyReference AssemblyPath="System.Windows.Forms" />
<AssemblyReference AssemblyPath="System.Xml" />
<AssemblyReference AssemblyPath="Microsoft.SqlServer.ManagedDTS.dll" />
<AssemblyReference AssemblyPath="Microsoft.SqlServer.ScriptTask.dll" />
</AssemblyReferences>
</ScriptTaskProject>
</ScriptProjects>
</Biml>
I thought I could simplify call GetBiml() the variable variable but that is going to emit the exact biml it was defined with
<Variable Name="ETLProcessStepID" DataType="Int32">0</Variable>
<Variable Name="TenantID" DataType="Int32">1</Variable>
and if that didn't have the actual value in there, it'd be legit syntax for the ReadOnly/ReadWrite variables collection. Que lástima.
Biml Script Component Source
Biml Test for echo aka Script Task

MEF plugins with their own configuration files?

I'm trying to load plugins at runtime and access their configuration files. The configuration sections in their config files are mapped to classes derived from ConfigurationElementCollection, ConfigurationElement and ConfigurationSection. The plugins and their configuration files are location in a subfolder called "Plugins".
The problem is that I can't seem to load the plugin configuration data and deserialize it into their respective classes correctly.
Here is an example of a plugin config for the plugin EmailPlugin.dll:
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<configSections>
<section name="EmailConfigurationSection" type="Foo.Plugins.EmailConfigurationSection, EmailPlugin, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" allowDefinition="Everywhere" allowExeDefinition="MachineToApplication" restartOnExternalChanges="true"/>
</configSections>
<EmailConfigurationSection server="192.168.0.10">
<EmailSettings>
<add keyword="ERROR"
sender="error#error.com"
recipients="foo#bar.com, wiki#waki.com"
subject = "Error occurred"
body = "An error was detected"
/>
</EmailSettings>
</EmailConfigurationSection>
</configuration>
I load this using this code:
private static System.Configuration.Configuration config = null;
public static System.Configuration.Configuration CurrentConfiguration
{
get
{
if (config == null)
{
Assembly assembly = Assembly.GetAssembly(typeof(EmailPlugin));
string directory = Path.GetDirectoryName(assembly.CodeBase);
string filename = Path.GetFileName(assembly.CodeBase);
string assemblyPath = Path.Combine(directory, filename);
config = ConfigurationManager.OpenExeConfiguration(new Uri(assemblyPath).LocalPath);
}
return config;
}
}
This results in the error:
An error occurred creating the configuration section handler for EmailConfigurationSection: Could not load file or assembly 'EmailPlugin, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null' or one of its dependencies. The system cannot find the file specified.
I added this to the top of the config file:
<runtime>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<probing privatePath="Plugins"/>
</assemblyBinding>
</runtime>
So the DLL is found, but it doesn't not cast to the proper class when I try to retrieve it:
EmailConfigurationSection defaults = CurrentConfiguration.Sections["EmailConfigurationSection"] as EmailConfigurationSection;
It always returns null. I know it's looking at the correct location and configuration file because I can retrieve the XML using this code:
var section = CurrentConfiguration.Sections["EmailConfigurationSection"];
string configXml = section.SectionInformation.GetRawXml();
However, when I try to deserialize it with this code:
var serializer = new XmlSerializer(typeof(EmailConfigurationSection));
object result;
EmailConfigurationSection defaults;
using (TextReader reader = new StringReader(configXml))
{
defaults = (EmailConfigurationSection)serializer.Deserialize(reader);
}
... I get an exception:
There was an error reflecting type 'Foo.Plugins.EmailConfigurationSection'.
This is the contents of the InnerException:
You must implement a default accessor on System.Configuration.ConfigurationLockCollection because it inherits from ICollection.
I assume it's referring to the class EmailConfigElementCollection, but then the message does not make sense because this class does have a default accessor:
public EmailConfigElement this[int index]
{
get
{
return (EmailConfigElement)BaseGet(index);
}
set
{
if (BaseGet(index) != null)
{
BaseRemoveAt(index);
}
BaseAdd(index, value);
}
}
I've used this code successfully in other projects (even with separate DLLs/configs), but this is the first time I'm trying to use it with MEF. Does anyone know what the problem is, or a suitable workaround?
I'm using .NET 4.5
I fixed this with the following modification:
public static System.Configuration.Configuration CurrentConfiguration
{
get
{
if (config == null)
{
// Added the next bit
AppDomain.CurrentDomain.AssemblyResolve += (o, args) =>
{
var loadedAssemblies = AppDomain.CurrentDomain.GetAssemblies();
return loadedAssemblies.Where(asm => asm.FullName == args.Name)
.FirstOrDefault();
};
Assembly assembly = Assembly.GetAssembly(typeof(EmailPlugin));
string directory = Path.GetDirectoryName(assembly.CodeBase);
string filename = Path.GetFileName(assembly.CodeBase);
string assemblyPath = Path.Combine(directory, filename);
config = ConfigurationManager.OpenExeConfiguration(new Uri(assemblyPath).LocalPath);
}
return config;
}
}
I got this from this question:
Custom configuration sections in MEF exporting assemblies. I had actually tried earlier it with no success.
The trick was that I had to move the runtime tag to the bottom of the XML configuration:
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<configSections>
<section name="EmailConfigurationSection" type="Foo.Plugins.EmailConfigurationSection, EmailPlugin, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" allowDefinition="Everywhere" allowExeDefinition="MachineToApplication" restartOnExternalChanges="true"/>
</configSections>
<EmailConfigurationSection server="255.255.255.1">
<EmailSettings>
<clear />
<add keyword="FOO"
sender="foo#foo.com"
recipients="me#you.com"
subject = "Foo occurred"
body = "Hello"
/>
</EmailSettings>
</EmailConfigurationSection>
<runtime>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<probing privatePath="Plugins"/>
</assemblyBinding>
</runtime>
</configuration>