MEF plugins with their own configuration files? - configuration

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>

Related

Unrecognized attribute 'name' when using <remove /> in a ConfigurationElementCollection

I have a custom ConfigurationSection that contains a custom ConfigurationElementCollection with custom ConfigurationElement instances. The <add> and <clear> tags work fine in the configuration file, but using <remove> generates the following exception during my unit test:
System.Configuration.ConfigurationErrorsException: Unrecognized attribute 'name'. Note that attribute names are case-sensitive.
The configuration file is pretty simple. Here's the inner-part of the test section in question:
<exceptionHandling>
<policies>
<clear />
<add name="default" shouldLog="true" shouldShow="true"/>
<add name="initialization" shouldLog="true" shouldShow="true"/>
<add name="security" shouldLog="true" shouldShow="true"/>
<add name="logOnly" shouldLog="true" shouldShow="false"/>
<add name="unhandled" shouldLog="true" shouldShow="true"/>
<add name="test" shouldLog="false" shouldShow="false"/>
<remove name="test"/>
</policies>
</exceptionHandling>
The following is the relevant code for the configuration classes (some has been elided for brevity).
public sealed class ExceptionHandlingSection : ConfigurationSection
{
[ConfigurationProperty("policies", IsDefaultCollection = false)]
[ConfigurationCollection(typeof(PolicyElementCollection), AddItemName = "add", RemoveItemName = "remove", ClearItemsName = "clear")]
public PolicyElementCollection Policies => (PolicyElementCollection)base["policies"];
}
public sealed class PolicyElementCollection : ConfigurationElementCollection
{
// pretty boiler plate
}
public sealed class PolicyElement : ConfigurationElement
{
[ConfigurationProperty("name", IsRequired = true)]
public string Name
{
get => (string)this["name"];
set => this["name"] = value;
}
// other properties
}
What needs to be done to get <remove> to work as shown in the test configuration file?
The answer to this turns out to be really simple.
I read a few things talking about XML attributes, but fundamentally it's looking for a key property. This can be assigned by a property of the ConfigurationPropertyAttribute. To get the <remove> tag to work all I need to do is change my ConfigurationElement class as follows:
public sealed class PolicyElement : ConfigurationElement
{
[ConfigurationProperty("name", IsKey = true, IsRequired = true)]
public string Name
{
get => (string)this["name"];
set => this["name"] = value;
}
// other properties
}

Struts 2 Download a pdf or word doc file stored into MySql Database as Blob Type [duplicate]

This is my struts.xml file.
<action name="sample" class="com.action.getPdf" method="getPdf">
<result name="success" type="stream">
<param name="inputName">fileInputStream</param>
<param name="contentType">application/pdf</param>
<param name="contentDisposition">attachment;filename="${fileName}"</param>
<param name="bufferSize">1024</param>
</result>
</action>
and this is action code where the object of File is getting null.
public String getPdf()throws Exception
{
Session ss = HibernateUtils.getSess();
Transaction t=ss.beginTransaction();
HttpSession httpsession=request.getSession();
String path2=request.getParameter("path1");
ServletContext servletContext = ServletActionContext.getServletContext();
//String path3=servletContext.getRealPath(path2);
System.out.println("the relative path of the file is:"+path2);
try
{
File fileToDownload = new File(path2);
fileInputStream = new FileInputStream(fileToDownload);
}
catch (Exception e)
{
if (t!=null)
{
t.rollback();
e.printStackTrace();
}
}
finally
{
ss.close();
}
return "success";
}
I have stored the file which I want to download in web content folder and I have stored the path of it in the database.
The problem with
String path2=request.getParameter("path1");
This method may return null if parameter path1 is missing. If it's not null then it should be a valid path to the readable file that you application has access.
Read the example: How to read file in Java – FileInputStream. You can trace the output with the code.
System.out.println("Total file size to read (in bytes) : "
+ getFileInputStream().available());
The getter is needed to return a stream result, and as you are using dynamic parameter in the result config. You should provide the getter for fileName.
I have solved this question. I have stored the physical path of the file in the data base.For example if your project path is :
D:/Workspace_ABC/SampleProject/WebContent/D-Files/APJ.AbdulKalam.pdf
then store this path as it is in the database table. and then use this path to download file.

Insert XmlFile (or other) from camel route to mongoDB

I've been trying to insert a XML file into mongoDB with camel and I can't manage to make it work.
I've followed this tutorial for the first steps:
http://www.pretechsol.com/2014/09/apache-camel-mongodb-component-example.html
In my route, I convert it in JSON then use 'convertBodyTo(string.class) for mongo to recognize the file.
The code works well with regular route (sending the file to another folder for example). But when I run it for mongoDB, all I get in the console is my Process message again and again with my databased never being filled.As I don't receive any error message, I don't know how to find where the problem come from.
The mongoDB name, ip, users, password have been already checked multiple times.
I would be very grateful if someone could help me on this one. Here is the files I am using. (I will spare you the process file).
camel-context.xml:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://camel.apache.org/schema/spring
http://camel.apache.org/schema/spring/camel-spring.xsd">
<bean id="myDb" class="com.mongodb.Mongo">
<constructor-arg index="0">
<bean class="com.mongodb.MongoURI">
<constructor-arg index="0"
value="mongodb://username:password#192.168.3.29:27017/db" />
</bean>
</constructor-arg>
</bean>
<bean id="mongodb" class="org.apache.camel.component.mongodb.MongoDbComponent"></bean>
<camelContext xmlns="http://camel.apache.org/schema/spring">
<routeBuilder ref="camelRoute" />
</camelContext>
<bean id="camelRoute" class="infotel.camel.project01.CamelRoute" />
Here is my RoutingFile:
#Component
public class CamelRoute extends SpringRouteBuilder {
final Processor myProcessor = new MyProcessor();
final Processor myProcessorMongo = new MyProcessorMongo();
final XmlJsonDataFormat xmlJsonFormat = new XmlJsonDataFormat();
#Override
public void configure() {
xmlJsonFormat.setForceTopLevelObject(true);
from("file:xml_files?noop=true").marshal(xmlJsonFormat).convertBodyTo(String.class).process(myProcessorMongo)
.to("mongodb:myDb?database=test_bignav&collection=doc&operation=insert");
}
}
And finally here is my main:
public class MyMain {
public static void main(String[] args) throws Exception {
ApplicationContext context =
new ClassPathXmlApplicationContext("META-INF/spring/camel-context.xml");
}
}
Thanks a lot.
Edit:
Here is MyProcessorMongo edited to get the error:
public class MyProcessorMongo implements Processor{
public void process(Exchange exchange) throws Exception {
System.out.println("\n file transfered to mongo: "+ exchange.getIn().getHeader("CamelFileName"));
exchange.getProperty(Exchange.EXCEPTION_CAUGHT, Exception.class).printStackTrace();
}
}
Enable tracing with trace="true":
<camelContext trace="true" xmlns="http://camel.apache.org/schema/spring">
Dirty but quick, to get the error you can add this to you configure() method before your from :
.onException(Exception.class).handled(true).process(new Processor() {
#Override
public void process(Exchange exchange) {
exchange.getProperty(Exchange.EXCEPTION_CAUGHT, Exception.class).printStackTrace();
}
})
The handled(true) prevents your message from being processed again and again.
thanks for your help I have been able to get the error message.
The problem actually came from mongoDB itself and not camel or code. With the change on users the connection works and I'm able to insert document inside a collection.
Remove the ".process(myProcessorMongo)" from route configuration . Input xml-> json conversion->string conversion -> Mongodb. Above route will work. And you are passing the exchange object to myProcessorMongo but Out message is null so nothing will be inserted into MongoDB . Put exchange.getOut().getBody(); in the myProcessorMongo and print it.If its coming as null u have to get the input message from exchange Obj and set it back it in to Out message property in the exchange Object.

How to make a .html file accessible through HTTP but only through a redirect?

We have a file on our server that's accessible directly through the URL, but it's a security issue at this point.
Our system opens the file in a pop-up window, but you can also get directly to the page by navigating directly to its URL.
How can we prevent this and only allow access to the file through a redirect?
Set a Session variable on the page that opens the popup:
Session["MainPageVisited"] = true;
And on the popup page check this value:
if (Session["MainPageVisited"] == null || !Session["MainPageVisited"])
{
Response.Redirect("http://www.example.com/", true);
}
For this solution to work your html file will need to be served as an aspx. Alternatively, you could create a HTTP Module if you need it to be an actual html:
Create Module
using System;
using System.Web;
public class HelloWorldModule : IHttpModule
{
public HelloWorldModule()
{
}
public String ModuleName
{
get { return "HelloWorldModule"; }
}
// In the Init function, register for HttpApplication
// events by adding your handlers.
public void Init(HttpApplication application)
{
application.BeginRequest +=
(new EventHandler(this.Application_BeginRequest));
}
private void Application_BeginRequest(Object source,
EventArgs e)
{
// Create HttpApplication and HttpContext objects to access
// request and response properties.
HttpApplication application = (HttpApplication)source;
HttpContext context = application.Context;
string filePath = context.Request.FilePath;
string fileExtension =
VirtualPathUtility.GetExtension(filePath);
if (fileExtension.Equals(".html"))
{
if (Session["MainPageVisited"] == null || !Session["MainPageVisited"])
{
// Handle it
}
}
}
public void Dispose() { }
}
To register the module for IIS 6.0 and IIS 7.0 running in Classic mode
<configuration>
<system.web>
<httpModules>
<add name="HelloWorldModule" type="HelloWorldModule"/>
</httpModules>
</system.web>
</configuration>
To register the module for IIS 7.0 running in Integrated mode
<configuration>
<system.webServer>
<modules>
<add name="HelloWorldModule" type="HelloWorldModule"/>
</modules>
</system.webServer>
</configuration>
Note, this was created without testing but it should put you on the right track. Make sure that all requests are mapped through ASP.NET for this to work (Integrated mode or set wildcard application mappings).

Windows Service: EF + MySql without app.config

At the moment I'm writing a windows service.
I'm already using EntityFramework with MSSQL database which is working perfectly. Now I have to use MySql parallely. But I can't manage to get it running ... I would like to avoid using the app.config and configure EntityFramework via the constructor of my class derived from DbContext.
I have a SqlContext class:
public class SqlContext : DbContext
{
public IDbSet<ServiceauftragSource> ServiceauftragSource { get; set; }
public SqlContext(string connectionString)
: base(connectionString)
{
}
public SqlContext(DbConnection connection)
: base(connection, true)
{
this.Configuration.LazyLoadingEnabled = true;
}
}
In the constructor of my UnitOfWork I try to create my SqlContext:
public SqlUnitOfWork()
{
const string connStr = "server=127.0.0.1;uid=myuser;pwd=mypw;database=mydb;";
MySqlConnection conn = new MySqlConnection(connectionString);
this.Context = new SqlContext(conn);
}
This didn't work. I get the following message when trying to access the database:
Unable to determine the DbProviderFactory type for connection of type 'MySql.Data.MySqlClient.MySqlConnection'. Make sure that the ADO.NET provider is installed or registered in the application config.
Neither did:
public SqlUnitOfWork()
{
this.SetConnectionString();
this.Context = new SqlContext(connectionString);
}
private void SetConnectionString()
{
this.connectionString = "Data Source=" + debugDatabaseServer + ";Initial Catalog=" + debugDatabaseName
+ ";User ID=" + debugDatabaseUsername + ";Password=" + debugDatabasePassword
+ ";Trusted_Connection=False;Persist Security Info=True;";
}
I'm not sure why but I think this is because I haven't told my context its provider (according to other threads on SO it has to be MySql.Data.MySqlClient). But where and how to?
References of the project:
Update
I tried to use my App.config:
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<configSections>
<!-- For more information on Entity Framework configuration, visit http://go.microsoft.com/fwlink/?LinkID=237468 -->
<section name="entityFramework" type="System.Data.Entity.Internal.ConfigFile.EntityFrameworkSection, EntityFramework, Version=6.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" requirePermission="false" />
</configSections>
<connectionStrings>
<add name="MySqlContext" providerName="MySql.Data.MySqlClient" connectionString="server=localhost;
port=3306;database=***;uid=***;password=***"/>
</connectionStrings>
<entityFramework>
<defaultConnectionFactory type="System.Data.Entity.Infrastructure.SqlConnectionFactory, EntityFramework" />
<providers>
<provider invariantName="MySql.Data.MySqlClient" type="MySql.Data.MySqlClient.MySqlProviderServices,
MySql.Data.Entity.EF6" />
<provider invariantName="System.Data.SqlClient" type="System.Data.Entity.SqlServer.SqlProviderServices, EntityFramework.SqlServer" />
</providers>
</entityFramework>
</configuration>
When accessing the database I get the following error:
Object reference not set to an instance of an object.
I think this is because my service can't access the app.config after it's being installed. I ensured that app.config is MyService.exe.config after build ...
I've been using MySQL with EF for quite some time pretty flawlessly, what yo need to do is add the following line above your DBContext
[DbConfigurationType(typeof (MySql.Data.Entity.MySqlEFConfiguration))]
so in your case the snippet would look like
[DbConfigurationType(typeof (MySql.Data.Entity.MySqlEFConfiguration))]
public class SqlContext : DbContext
{
public IDbSet<ServiceauftragSource> ServiceauftragSource { get; set; }
public SqlContext(string connectionString)
: base(connectionString)
{
}
public SqlContext(DbConnection connection)
: base(connection, true)
{
this.Configuration.LazyLoadingEnabled = true;
}
}
as for your connection string, i tend to store mine in the web config and then string format for whatever db,server or user i need to create a context for - hope that helps!
NB Im presuming you have your refs to MySQL.data and EF6 as well!