Encrypt custom section in app.config - c#

I want to encrypt the following section in my c# app.
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<configSections>
<section name="ConX" type="System.Configuration.NameValueSectionHandler" />
</configSections>
<ConX>
<add key="SqlSrv" value="0.0.0.0"/>
</ConX>
<startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5.2" />
</startup>
...
The following Code doesn't work, because the NameValueCollection does not support the ProtectSection Method.
How can I encrypt this section?
var section = ConfigurationManager.GetSection("ConX") as NameValueCollection;
section.SectionInformation.ProtectSection("RsaProtectedConfigurationProvider");
When I try the following line, the section object just stays null
AppSettingsSection section = ConfigurationManager.GetSection("ConX") as AppSettingsSection;
However, if i read the value like shown below it works, so the section can be found.
var section = ConfigurationManager.GetSection("ConX") as NameValueCollection;
var value = section["SqlSrv"];

I've just implemented the same thing for my website this week, you may find the following code useful.
Kind regards.
/// <summary>
/// Encrypts one or more sections of the web.config using the provided provider.
/// </summary>
/// <param name="protectionProvider">
/// Protection provider to use:
/// RsaProtectedConfigurationProvider or DPAPIProtectedConfigurationProvider.
/// </param>
/// <param name="sectionsToEncrypt">Array of section names to encrypt</param>
/// <returns>
/// On success returns true
/// On failure returns false
/// </returns>
public static bool EncryptConfigurationSections(
string protectionProvider,
params string[] sectionsToEncrypt
) {
bool isOK = true;
List<string> SUPPORTED_PROVIDERS = new List<string>() {
"RsaProtectedConfigurationProvider",
"DPAPIProtectedConfigurationProvider"
};
if (!SUPPORTED_PROVIDERS.Contains(protectionProvider)) {
throw new ArgumentException("Provided provider is not supported.", "protectionProvider");
}
try {
Configuration webConfiguration = null;
bool saveRequired = false;
// OpenWebConfiguration call will find the web.config file, we only need the directory (~)
webConfiguration = System.Web.Configuration.WebConfigurationManager.OpenWebConfiguration("~");
// Protect all specified sections
// ... Do all that apply in one go so we only have the hit of saving once
foreach (string sectionToEncrypt in sectionsToEncrypt) {
ConfigurationSection configSection = webConfiguration.GetSection(sectionToEncrypt);
// No point encrypting if it's already been done
if (configSection != null && !configSection.SectionInformation.IsProtected) {
saveRequired = true;
configSection.SectionInformation.ProtectSection(protectionProvider);
configSection.SectionInformation.ForceSave = true;
}
}
if (saveRequired) {
// Only save if there's a section which was not protected
// ... again, no point taking the hit if we don't need to
webConfiguration.Save(ConfigurationSaveMode.Modified);
}
}
catch (Exception e) {
isOK = false;
}
return isOK;
} // EncryptConfigurationSections

Related

How to configure log4net with VSTO project in visual studio

I was trying to build a simple outlook add in.
I created a VSTO project in Visual Studio 2017.
When the project is created by the Visual Studio, there is no app.config or web.config in the solution. I want to use log4net for this project. How should I configure it? I tried to add web.config or app.config for the project. But I was not able to get values from the configure file. I think the project cannot recognize them. I cannot use ConfigurationManager.AppSettings["key"] to get the value from the configure file.
Does anyone know how to use log4net in VSTO project?
Thank you.
Install Log4Net through the NuGet Package Manager. Then create a setting in the project properties, like asdf (you can delete the setting after you update the app.config file with the Log4Net sections), it will then create the app.config for you.
Here's the configuration in app.config for Log4Net I use on most of my projects. I created a new project with a setting asdf and added my standard Log4Net setup.
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<configSections>
<sectionGroup name="userSettings" type="System.Configuration.UserSettingsGroup, System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" >
<section name="ExcelAddIn1.Properties.Settings" type="System.Configuration.ClientSettingsSection, System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" allowExeDefinition="MachineToLocalUser" requirePermission="false" />
</sectionGroup>
<section name="log4net" type="log4net.Config.Log4NetConfigurationSectionHandler, log4net"/>
</configSections>
<userSettings>
<ExcelAddIn1.Properties.Settings>
<setting name="asdf" serializeAs="String">
<value>asdf</value>
</setting>
</ExcelAddIn1.Properties.Settings>
</userSettings>
<log4net>
<appender name="ConsoleAppender" type="log4net.Appender.ConsoleAppender">
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="%date [%thread] %-5level %logger [%ndc] - %message%newline"/>
</layout>
</appender>
<appender name="FileAppender" type="log4net.Appender.FileAppender">
<file value="C:\Temp\MyOfficeAddIn.log"/>
<appendToFile value="true"/>
<lockingModel type="log4net.Appender.FileAppender+MinimalLock"/>
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="%date|%-5level|%message%newline"/>
</layout>
</appender>
<root>
<level value="ALL"/>
<appender-ref ref="FileAppender"/>
</root>
</log4net>
</configuration>
I usually create a class called ErrorHandler and add the following code.
using System;
using System.Linq;
using System.Text;
using System.Collections.Generic;
using System.Windows.Forms;
using System.Runtime.InteropServices;
using Excel = Microsoft.Office.Interop.Excel;
using log4net;
using log4net.Config;
[assembly: log4net.Config.XmlConfigurator(Watch = true)]
Then I use the following methods to write to the log file
private static readonly ILog log = LogManager.GetLogger(typeof(ErrorHandler));
/// <summary>
/// Applies a new path for the log file by FileAppender name
/// </summary>
public static void SetLogPath()
{
XmlConfigurator.Configure();
log4net.Repository.Hierarchy.Hierarchy h = (log4net.Repository.Hierarchy.Hierarchy)LogManager.GetRepository();
string logFileName = System.IO.Path.Combine(Properties.Settings.Default.App_PathLocalData, AssemblyInfo.Product + ".log");
foreach (var a in h.Root.Appenders)
{
if (a is log4net.Appender.FileAppender)
{
if (a.Name.Equals("FileAppender"))
{
log4net.Appender.FileAppender fa = (log4net.Appender.FileAppender)a;
fa.File = logFileName;
fa.ActivateOptions();
}
}
}
}
/// <summary>
/// Create a log record to track which methods are being used.
/// </summary>
public static void CreateLogRecord()
{
try
{
// gather context
var sf = new System.Diagnostics.StackFrame(1);
var caller = sf.GetMethod();
var currentProcedure = caller.Name.Trim();
// handle log record
var logMessage = string.Concat(new Dictionary<string, string>
{
["PROCEDURE"] = currentProcedure,
["USER NAME"] = Environment.UserName,
["MACHINE NAME"] = Environment.MachineName
}.Select(x => $"[{x.Key}]=|{x.Value}|"));
log.Info(logMessage);
}
catch (Exception ex)
{
ErrorHandler.DisplayMessage(ex);
}
}
/// <summary>
/// Used to produce an error message and create a log record
/// <example>
/// <code lang="C#">
/// ErrorHandler.DisplayMessage(ex);
/// </code>
/// </example>
/// </summary>
/// <param name="ex">Represents errors that occur during application execution.</param>
/// <param name="isSilent">Used to show a message to the user and log an error record or just log a record.</param>
/// <remarks></remarks>
public static void DisplayMessage(Exception ex, Boolean isSilent = false)
{
// gather context
var sf = new System.Diagnostics.StackFrame(1);
var caller = sf.GetMethod();
var errorDescription = ex.ToString().Replace("\r\n", " "); // the carriage returns were messing up my log file
var currentProcedure = caller.Name.Trim();
var currentFileName = AssemblyInfo.GetCurrentFileName();
// handle log record
var logMessage = string.Concat(new Dictionary<string, string>
{
["PROCEDURE"] = currentProcedure,
["USER NAME"] = Environment.UserName,
["MACHINE NAME"] = Environment.MachineName,
["FILE NAME"] = currentFileName,
["DESCRIPTION"] = errorDescription,
}.Select(x => $"[{x.Key}]=|{x.Value}|"));
log.Error(logMessage);
// format message
var userMessage = new StringBuilder()
.AppendLine("Contact your system administrator. A record has been created in the log file.")
.AppendLine("Procedure: " + currentProcedure)
.AppendLine("Description: " + errorDescription)
.ToString();
// handle message
if (isSilent == false)
{
MessageBox.Show(userMessage, "Unexpected Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
}
I have a project in GitHub you can use as an example.

how to read encrypted app.config appSettings in win console application?

EDIT: This question made no sense. I mixed .vshost.config with exe.config. What to do with this?
Program.cs main:
databaseName = System.Configuration.ConfigurationManager.AppSettings["DatabaseName"];
databaseUser = System.Configuration.ConfigurationManager.AppSettings["DatabaseUser"];
databasePwd = System.Configuration.ConfigurationManager.AppSettings["DatabasePassword"];
port = System.Configuration.ConfigurationManager.AppSettings["Port"];
logDirectory = System.Configuration.ConfigurationManager.AppSettings["LogDirectory"];
strLogLevel = System.Configuration.ConfigurationManager.AppSettings["LogLevel"];
EncryptConfigSection("appSettings");
This is how I encrypt the file:
private static void EncryptConfigSection(string sectionKey)
{
Configuration config = ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None);
ConfigurationSection section = config.GetSection(sectionKey);
if (section != null)
{
if (!section.SectionInformation.IsProtected)
{
if (!section.ElementInformation.IsLocked)
{
section.SectionInformation.ProtectSection("DataProtectionConfigurationProvider");
section.SectionInformation.ForceSave = true;
config.Save(ConfigurationSaveMode.Full);
}
}
}
}
The file gets duplicated and encrypted just like in the examples I found in web:
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<appSettings configProtectionProvider="DataProtectionConfigurationProvider">
<EncryptedData>
<CipherData>
<CipherValue>AQAAANCMnd8BFdERjHoAwE/Cl+sBAAAAvsQ9Wtc58EC5EZCEq91EogQAAAACAAAAAAADZgAAwAAAABAAAClVHhpR5xAw4KFNyrANtavAAAAAASAAACgAAAAEAAAABHkhg2ztiY3bdWhTG9iy6twAAAAF5mAHt7oDQWCgc1iLL2hYUJZgmquU8XsojjqXVQdV1CaW3XEBXBDhN30DEZizP3F5rGGMCjL9CVjHfsPAfvVYyRHCcup22BoByb5y/MDujaASpaWZYcdxSxLijT/Zq3zB8hiWyWPruY0G7emYEOq/xQAAADkgStCMABwo3oZx/VXHD41wrsjXg==</CipherValue>
</CipherData>
</EncryptedData>
</appSettings>
</configuration>
But next time I start it, I can't read it. All read values are null. I naturally removed the original, unencrypted file from the folder.
You can use the KeyValueConfigurationCollection for the appSettings key and the ConnectionStringSettingsCollection for the connectionStrings key.
This encrypts when not encrypted and decrypts and prints out values when encrypted:
private static void CryptConfig (string[] sectionKeys)
{
Configuration config = ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None);
foreach (string sectionKey in sectionKeys)
{
ConfigurationSection section = config.GetSection(sectionKey);
if (section != null)
{
if (section.ElementInformation.IsLocked)
{
Console.WriteLine("Section: {0} is locked", sectionKey);
}
else
{
if (!section.SectionInformation.IsProtected)
{
//%windir%\system32\Microsoft\Protect\S-1-5-18
section.SectionInformation.ProtectSection(DPCP);
section.SectionInformation.ForceSave = true;
Console.WriteLine("Encrypting: {0} {1}", section.SectionInformation.Name, section.SectionInformation.SectionName);
}
else
{ // display values for current config application name value pairs
foreach (KeyValueConfigurationElement x in config.AppSettings.Settings)
{
Console.WriteLine("Key: {0} Value:{1}", x.Key, x.Value);
}
foreach (ConnectionStringSettings x in config.ConnectionStrings.ConnectionStrings)
{
Console.WriteLine("Name: {0} Provider:{1} Cs:{2}", x.Name, x.ProviderName, x.ConnectionString);
}
//
section.SectionInformation.UnprotectSection();
section.SectionInformation.ForceSave = true;
Console.WriteLine("Decrypting: {0} {1}", section.SectionInformation.Name, section.SectionInformation.SectionName);
}
}
}
else
{
Console.WriteLine("Section: {0} is null", sectionKey);
}
}
//
config.Save(ConfigurationSaveMode.Full);
Console.WriteLine("Saving file: {0}", config.FilePath);
}
App.config used:
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<appSettings>
<add key="DatabaseName" value="databaseName"/>
<add key="DatabaseUser" value="databaseUser"/>
<add key="DatabasePassword" value="databasePwd"/>
<add key="Port" value="port"/>
<add key="LogDirectory" value="logDirectory"/>
<add key="LogLevel" value="strLogLevel"/>
</appSettings>
<connectionStrings>
<add name="SecurePassDataBase" connectionString="Data Source=D-xxxx;Initial Catalog=DEMO;User ID=sa;Password=******" />
</connectionStrings>
</configuration>
here is a very simple code you can use
RsaProtectedConfigurationProvider Sample
Made some small modifications...
static public void ProtectSection()
{
// Get the current configuration file.
System.Configuration.Configuration config =
ConfigurationManager.OpenExeConfiguration(
ConfigurationUserLevel.None);
// Get the section.
ConfigurationSection section = config.GetSection("appSettings");
// Protect (encrypt)the section.
section.SectionInformation.ProtectSection("RsaProtectedConfigurationProvider");
// Save the encrypted section.
section.SectionInformation.ForceSave = true;
config.Save(ConfigurationSaveMode.Full);
// Display decrypted configuration
// section. Note, the system
// uses the Rsa provider to decrypt
// the section transparently.
string sectionXml = section.SectionInformation.GetRawXml();
Console.WriteLine("Decrypted section:");
Console.WriteLine(sectionXml);
}
Try this & you'll get what you want :
Console.WriteLine(ConfigurationManager.OpenExeConfiguration(Path.GetFileName(Assembly.GetExecutingAssembly().CodeBase).ToString()).FilePath.ToString());
string[] readText = File.ReadAllLines(ConfigurationManager.OpenExeConfiguration(Path.GetFileName(Assembly.GetExecutingAssembly().CodeBase).ToString()).FilePath.ToString());
foreach (string s in readText)
{
Console.WriteLine(s);
}

Simple way to perform error logging?

I've created a small C# winforms application, as an added feature I was considering adding some form of error logging into it. Anyone have any suggestions for good ways to go about this? This is a feature I've never looked into adding to previous projects, so I'm open to suggestions from Developers who have more experience.
I was considering something along the lines of writing exceptions to a specified text file, or possibly a database table. This is an application that will be in use for a few months and then discarded when a larger product is finished.
I wouldn't dig too much on external libraries since your logging needs are simple.
.NET Framework already ships with this feature in the namespace System.Diagnostics, you could write all the logging you need there by simply calling methods under the Trace class:
Trace.TraceInformation("Your Information");
Trace.TraceError("Your Error");
Trace.TraceWarning("Your Warning");
And then configure all the trace listeners that fit your needs on your app.config file:
<configuration>
// other config
<system.diagnostics>
<trace autoflush="true" indentsize="4">
<listeners>
<add name="consoleListener" type="System.Diagnostics.ConsoleTraceListener"/>
<add name="textWriterListener" type="System.Diagnostics.TextWriterTraceListener" initializeData="YourLogFile.txt"/>
<add name="eventLogListener" type="System.Diagnostics.EventLogTraceListener" initializeData="YourEventLogSource" />
<remove name="Default"/>
</listeners>
</trace>
</system.diagnostics>
// other config
</configuration>
or if you prefer, you can also configure your listeners in your application, without depending on a config file:
Trace.Listeners.Add(new TextWriterTraceListener("MyTextFile.log"));
Remember to set the Trace.AutoFlush property to true, for the Text log to work properly.
You could use SimpleLog.
It's a simple, but robust and powerful one-class logging solution, easy to understand, easy to integrate and easy to use. No need to spend days for setting up and customize log4Net, with that class, you're done in minutes.
Though it currently logs to a file, it should be easily customizable to log to a database.
http://www.codeproject.com/Tips/585796/Simple-Log
An optimal solution, in my opinion, would be to use NLog: http://nlog-project.org/
Just install the config package from NuGet: http://www.nuget.org/packages/NLog.Config/ and you will end up with the library and a pre-configured file logger...
Then in your code you just need:
// A logger member field:
private readonly Logger logger = LogManager.GetCurrentClassLogger(); // creates a logger using the class name
// use it:
logger.Info(...);
logger.Error(...);
// and also:
logger.ErrorException("text", ex); // which will log the stack trace.
In the config file you get, you need to uncomment the sections that you need:
<nlog xmlns="http://www.nlog-project.org/schemas/NLog.xsd"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<!--
See http://nlog-project.org/wiki/Configuration_file
for information on customizing logging rules and outputs.
-->
<targets>
<!-- add your targets here -->
<!-- UNCOMMENT THIS!
<target xsi:type="File" name="f" fileName="${basedir}/logs/${shortdate}.log"
layout="${longdate} ${uppercase:${level}} ${message}" />
-->
</targets>
<rules>
<!-- add your logging rules here -->
<!-- UNCOMMENT THIS!
<logger name="*" minlevel="Trace" writeTo="f" />
-->
</rules>
</nlog>
Edit the properties of the nlog.config file to
Copy to Output Directory: Copy always
Create a class called Log.cs
I am using Linq To SQl to save to the database
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Text;
public static partial class Log
{
/// <summary>
/// Saves the exception details to ErrorLogging db with Low Priority
/// </summary>
/// <param name="ex">The exception.</param>
public static void Save(this Exception ex)
{
Save(ex, ImpactLevel.Low, "");
}
/// <summary>
/// Saves the exception details to ErrorLogging db with specified ImpactLevel
/// </summary>
/// <param name="ex">The exception.</param>
/// <param name="impactLevel">The Impact level.</param>
public static void Save(this Exception ex, ImpactLevel impactLevel)
{
Save(ex, impactLevel,"");
}
/// <summary>
/// Saves the exception details to ErrorLogging db with specified ImpactLevel and user message
/// </summary>
/// <param name="ex">The exception</param>
/// <param name="impactLevel">The impact level.</param>
/// <param name="errorDescription">The error Description.</param>
public static void Save(this Exception ex, ImpactLevel impactLevel, string errorDescription)
{
using (var db = new ErrorLoggingDataContext())
{
Log log = new Log();
if (errorDescription != null && errorDescription != "")
{
log.ErrorShortDescription = errorDescription;
}
log.ExceptionType = ex.GetType().FullName;
var stackTrace = new StackTrace(ex, true);
var allFrames = stackTrace.GetFrames().ToList();
foreach (var frame in allFrames)
{
log.FileName = frame.GetFileName();
log.LineNumber = frame.GetFileLineNumber();
var method = frame.GetMethod();
log.MethodName = method.Name;
log.ClassName = frame.GetMethod().DeclaringType.ToString();
}
log.ImpactLevel = impactLevel.ToString();
try
{
log.ApplicationName = Assembly.GetCallingAssembly().GetName().Name;
}
catch
{
log.ApplicationName = "";
}
log.ErrorMessage = ex.Message;
log.StackTrace = ex.StackTrace;
if (ex.InnerException != null)
{
log.InnerException = ex.InnerException.ToString();
log.InnerExceptionMessage = ex.InnerException.Message;
}
log.IpAddress = ""; //get the ip address
if (System.Diagnostics.Debugger.IsAttached)
{
log.IsProduction = false;
}
try
{
db.Logs.InsertOnSubmit(log);
db.SubmitChanges();
}
catch (Exception eex)
{
}
}
}
}
Create the following table
USE [database Name]
GO
/****** Object: Table [dbo].[Log] Script Date: 9/27/2016 11:52:32 AM ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
SET ANSI_PADDING ON
GO
CREATE TABLE [dbo].[Log](
[LogId] [INT] IDENTITY(1,1) NOT NULL,
[ErrorDate] [DATETIME] NOT NULL CONSTRAINT [DF_Log_Date] DEFAULT (GETDATE()),
[ErrorShortDescription] [VARCHAR](1000) NULL,
[ExceptionType] [VARCHAR](255) NULL,
[FileName] [VARCHAR](1000) NULL,
[LineNumber] [INT] NULL,
[MethodName] [VARCHAR](255) NULL,
[ClassName] [VARCHAR](150) NULL,
[ImpactLevel] [VARCHAR](50) NOT NULL,
[ApplicationName] [VARCHAR](255) NULL,
[ErrorMessage] [VARCHAR](4000) NULL,
[StackTrace] [VARCHAR](MAX) NULL,
[InnerException] [VARCHAR](2000) NULL,
[InnerExceptionMessage] [VARCHAR](2000) NULL,
[IpAddress] [VARCHAR](150) NULL,
[IsProduction] [BIT] NOT NULL CONSTRAINT [DF_Log_IsProduction] DEFAULT ((1)),
[LastModified] [DATETIME] NOT NULL CONSTRAINT [DF_Log_LastModified] DEFAULT (GETDATE()),
CONSTRAINT [PK_Log] PRIMARY KEY CLUSTERED
(
[LogId] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY]
GO
SET ANSI_PADDING OFF
GO
EXEC sys.sp_addextendedproperty #name=N'MS_Description', #value=N'This table holds all the exceptions.
ErrorData = when error happened
,[ErrorShortDescription] == short desc about the error entered by the developers
,[FileName] = file where error happened full path
,[LineNumber] = line number where code failed
,[MethodName] = method name where exception happened
,[ClassName] = class where exception happened
,[ImpactLevel] = high, medium, low
,[ApplicationName] = name of the application where error came from
,[ErrorMessage] = exception error messge
,[StackTrace] = C# stack trace
,[InnerException] = inner exception of strack trace
,[InnerExceptionMessage] = inner message
,[IpAddress]
,[IsProduction]' , #level0type=N'SCHEMA',#level0name=N'dbo', #level1type=N'TABLE',#level1name=N'Log'
GO
Impact Level is basically Enum
public enum ImpactLevel
{
High = 0,
Medium = 1,
Low = 2,
}
You can use it as following
try
{
}
catch(Exception ex)
{
//this will save the exception details and mark exception as low priority
ex.Save();
}
try
{
}
catch(Exception ex)
{
//this will save the exception details with priority you define: High, Medium,Low
ex.Save(ImpactLevel.Medium);
}
try
{
}
catch(Exception ex)
{
//this will save the exception details with priority you define: High, Medium,Low
ex.Save(ImpactLevel.Medium, "You can enter an details you want here ");
}
Well log4net works like a brick. It may be a bit hard to configure, but its worth it. It also allows you to configure file locking of those log files etc.
http://www.codeproject.com/Articles/140911/log4net-Tutorial
After reading the suggestions here, I ended up using the following:
private void LogSystemError(string message)
{
EventLog.WriteEntry("YourAppName", message, EventLogEntryType.Error);
}
The EventLog class is available using System.Diagnostics.
I avoided the options of logging into files (e.g. "yourLogFile.txt") to avoid issues of concurrency of multiple threads logging errors, location of the file and access security, and the possible issues of having a file that grows too large.
Heres example for log4net:
Create a new console project called Log4NetTest
Add log4net [1.2.13] nuget package into project
Write following program:
using System.Threading.Tasks;
using log4net;
using System.Text;
using System.CollectionsGeneric;
using System;
namespace Log4NetTest
{
class Program
{
private static readonly ILog _logger = LogManager.GetLogger("testApp.LoggingExample");
static void Main(string[] args)
{
// Configure from App.config. This is marked as obsolete so you can also add config into separate config file
// and use log4net.Config.XmlConfigurator method to configure from xml file.
log4net.Config.DOMConfigurator.Configure();
_logger.Debug("Shows only at debug");
_logger.Warn("Shows only at warn");
_logger.Error("Shows only at error");
Console.ReadKey();
}
}
}
Change your app.config to following:
<!-- language: xml -->
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<configSections>
<section name="log4net" type="log4net.Config.Log4NetConfigurationSectionHandler, log4net" />
</configSections>
<startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5" />
</startup>
<log4net debug="false">
<appender name="LogFileAppender" type="log4net.Appender.FileAppender,log4net" >
<param name="File" value="myLog.log" />
<param name="AppendToFile" value="true" />
<layout type="log4net.Layout.PatternLayout,log4net">
<param name="ConversionPattern" value="%date [%thread] %-5level %logger %ndc - %message%newline" />
</layout>
</appender>
<root>
<priority value="ALL" />
<appender-ref ref="LogFileAppender" />
</root>
<category name="testApp.LoggingExample">
<priority value="ALL" />
</category>
</log4net>
</configuration>
5.Run application and you should find following file from bin\Debug folder:
2013-12-13 13:27:27,252 [8] DEBUG testApp.LoggingExample (null) - Shows only at debug
2013-12-13 13:27:27,280 [8] WARN testApp.LoggingExample (null) - Shows only at warn
2013-12-13 13:27:27,282 [8] ERROR testApp.LoggingExample (null) - Shows only at error
You just write out your exception errors to a text file. Write to Text File. One suggestion is to put the file you create in a userdata or appdata directory though, so you do not have to struggle with permissions.
Since this is only needed for a few months and will be discarded there is no reason to go overboard with DB. A simple text file should suffice.
Instead of using log4net which is an external library I have created my own simple class, highly customizable and easy to use (edit YOURNAMESPACEHERE with the namespace that you need).
CONSOLE APP
using System;
using System.IO;
namespace YOURNAMESPACEHERE
{
enum LogEvent
{
Info = 0,
Success = 1,
Warning = 2,
Error = 3
}
internal static class Log
{
private static readonly string LogSession = DateTime.Now.ToLocalTime().ToString("ddMMyyyy_HHmmss");
private static readonly string LogPath = AppDomain.CurrentDomain.BaseDirectory + "logs";
internal static void Write(LogEvent Level, string Message, bool ShowConsole = true, bool WritelogFile = true)
{
string Event = string.Empty;
ConsoleColor ColorEvent = Console.ForegroundColor;
switch (Level)
{
case LogEvent.Info:
Event = "INFO";
ColorEvent = ConsoleColor.White;
break;
case LogEvent.Success:
Event = "SUCCESS";
ColorEvent = ConsoleColor.Green;
break;
case LogEvent.Warning:
Event = "WARNING";
ColorEvent = ConsoleColor.Yellow;
break;
case LogEvent.Error:
Event = "ERROR";
ColorEvent = ConsoleColor.Red;
break;
}
if (ShowConsole)
{
Console.ForegroundColor = ColorEvent;
Console.WriteLine(" [{0}] => {1}", DateTime.Now.ToString("HH:mm:ss"), Message);
Console.ResetColor();
}
if (WritelogFile)
{
if (!Directory.Exists(LogPath))
Directory.CreateDirectory(LogPath);
File.AppendAllText(LogPath + #"\" + LogSession + ".log", string.Format("[{0}] => {1}: {2}\n", DateTime.Now.ToString("HH:mm:ss"), Event, Message));
}
}
}
}
NO CONSOLE APP (ONLY LOG)
using System;
using System.IO;
namespace YOURNAMESPACEHERE
{
enum LogEvent
{
Info = 0,
Success = 1,
Warning = 2,
Error = 3
}
internal static class Log
{
private static readonly string LogSession = DateTime.Now.ToLocalTime().ToString("ddMMyyyy_HHmmss");
private static readonly string LogPath = AppDomain.CurrentDomain.BaseDirectory + "logs";
internal static void Write(LogEvent Level, string Message)
{
string Event = string.Empty;
switch (Level)
{
case LogEvent.Info:
Event = "INFO";
break;
case LogEvent.Success:
Event = "SUCCESS";
break;
case LogEvent.Warning:
Event = "WARNING";
break;
case LogEvent.Error:
Event = "ERROR";
break;
}
if (!Directory.Exists(LogPath))
Directory.CreateDirectory(LogPath);
File.AppendAllText(LogPath + #"\" + LogSession + ".log", string.Format("[{0}] => {1}: {2}\n", DateTime.Now.ToString("HH:mm:ss"), Event, Message));
}
}
Usage:
CONSOLE APP
Log.Write(LogEvent.Info, "Test message"); // It will print an info in your console, also will save a copy of this print in a .log file.
Log.Write(LogEvent.Warning, "Test message", false); // It will save the print as warning only in your .log file.
Log.Write(LogEvent.Error, "Test message", true, false); // It will print an error only in your console.
NO CONSOLE APP (ONLY LOG)
Log.Write(LogEvent.Info, "Test message"); // It will print an info in your .log file.

Preserving proto comments when generating C# with protobuf-net

We are using protobuf-net to handle our Protocol Buffer needs in a C# application. Since we share our .proto files with other, non-managed applications, we are generating our code from the .proto files (not using the code-first protobuf-net approach). In order to stay as DRY as possible, we keep a lot of interface documentation inside the .proto files themselves. We generate the C# code by means of protogen.exe, called by a project build target.
Now, is there any way to (automatically) transfer these comments into the compiled C# code?
Basically, given a .proto like this:
// This message is used to request a resource from the server
message GetResource
{
// The identifier of the requested resource
required string resourceId = 1;
}
...I would like something like this (IExtensible methods omitted for readability):
/// <summary>
/// This message is used to request a resource from the server
/// </summary>
[global::System.Serializable,global::ProtoBuf.ProtoContract(Name=#"GetResource")]
public partial class GetResource : global::ProtoBuf.IExtensible
{
public GetResource() {}
private string _resourceId;
/// <summary>
/// The identifier of the requested resource
/// [Required] <-- Would be nice...
/// </summary>
[global::ProtoBuf.ProtoMember(1, IsRequired = true, Name=#"resourceId",
DataFormat = global::ProtoBuf.DataFormat.Default)]
public string ResourceId
{
get { return _resourceId; }
set { _resourceId = value; }
}
}
Actually current version does support comments. It may be enabled with --include_source_info.
Comments are available in descriptor.Location[n].leading_comments and trailing_comments :
https://code.google.com/p/protobuf/source/browse/trunk/src/google/protobuf/descriptor.proto
I've added the corresponding properties to protobuf-net Location class:
private string _leading_comments = "";
[global::ProtoBuf.ProtoMember(3, IsRequired = false, Name = #"leading_comments", DataFormat = global::ProtoBuf.DataFormat.Default)]
[global::System.ComponentModel.DefaultValue("")]
public string leading_comments
{
get { return _leading_comments; }
set { _leading_comments = value; }
}
private string _trailing_comments = "";
[global::ProtoBuf.ProtoMember(4, IsRequired = false, Name = #"trailing_comments", DataFormat = global::ProtoBuf.DataFormat.Default)]
[global::System.ComponentModel.DefaultValue("")]
public string trailing_comments
{
get { return _trailing_comments; }
set { _trailing_comments = value; }
}
And added --include_source_info to protoc call (ProtoBuf.CodeGenerator.InputFileLoader)
And locations with comments were added to xml generated:
<?xml version="1.0" encoding="utf-16"?>
<FileDescriptorSet xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<file>
<FileDescriptorProto>
<name>Test.proto</name>
<dependency />
<message_type>
<DescriptorProto>
<name>Test2</name>
<field>
<FieldDescriptorProto>
<name>IntValue</name>
<number>1</number>
<type>TYPE_INT32</type>
</FieldDescriptorProto>
</field>
<extension />
<nested_type />
<enum_type />
<extension_range />
</DescriptorProto>
</message_type>
<enum_type />
<service />
<extension />
<source_code_info>
<location>
...
<Location>
<path>
<int>4</int>
<int>0</int>
<int>2</int>
<int>0</int>
</path>
<span>
<int>1</int>
<int>0</int>
<int>28</int>
</span>
<trailing_comments> some comment
</trailing_comments>
</Location>
...
</location>
</source_code_info>
</FileDescriptorProto>
</file>
</FileDescriptorSet>
source .proto:
message Test2{
optional int32 IntValue = 1;// some comment
}
But I'm not strong in xslt to update ProtoGen/csharp.xslt to include comments into CS file generated
At the current time, I believe the answer is "no". To the best of my knowledge, "protoc" (Google's tool for parsing .proto files, which is used under the hood) silently discards the comments - so there is nothing available to read from. If a custom parser was written, then yes it would be possible, but there is also a language ambiguity about which comments apply to which lines, for example:
// this probably relates to resourceId
required string resourceId = 1;
required int foo = 2; // but... is this foo? or bar?
// and what about this?
// what does this relate to? and why?
// and this? what are the rules?
required int bar = 3;
So for 2 different reasons: at the moment, no. All suggestions considered, though... especially if they come with a custom parser included :)
Note that AFAIK this information is missing from most (all?) implementations for this reason. I'm happy to be corrected, though.

How to get the values of a ConfigurationSection of type NameValueSectionHandler

I'm working with C#, Framework 3.5 (VS 2008).
I'm using the ConfigurationManager to load a config (not the default app.config file) into a Configuration object.
Using the Configuration class, I was able to get a ConfigurationSection, but I could not find a way to get the values of that section.
In the config, the ConfigurationSection is of type System.Configuration.NameValueSectionHandler.
For what it worth, when I used the method GetSection of the ConfigurationManager (works only when it was on my default app.config file), I received an object type, that I could cast into collection of pairs of key-value, and I just received the value like a Dictionary. I could not do such cast when I received ConfigurationSection class from the Configuration class however.
EDIT:
Example of the config file:
<configuration>
<configSections>
<section name="MyParams"
type="System.Configuration.NameValueSectionHandler" />
</configSections>
<MyParams>
<add key="FirstParam" value="One"/>
<add key="SecondParam" value="Two"/>
</MyParams>
</configuration>
Example of the way i was able to use it when it was on app.config (the "GetSection" method is for the default app.config only):
NameValueCollection myParamsCollection =
(NameValueCollection)ConfigurationManager.GetSection("MyParams");
Console.WriteLine(myParamsCollection["FirstParam"]);
Console.WriteLine(myParamsCollection["SecondParam"]);
Suffered from exact issue. Problem was because of NameValueSectionHandler in .config file. You should use AppSettingsSection instead:
<configuration>
<configSections>
<section name="DEV" type="System.Configuration.AppSettingsSection" />
<section name="TEST" type="System.Configuration.AppSettingsSection" />
</configSections>
<TEST>
<add key="key" value="value1" />
</TEST>
<DEV>
<add key="key" value="value2" />
</DEV>
</configuration>
then in C# code:
AppSettingsSection section = (AppSettingsSection)ConfigurationManager.GetSection("TEST");
btw NameValueSectionHandler is not supported any more in 2.0.
Here's a good post that shows how to do it.
If you want to read the values from a file other than the app.config, you need to load it into the ConfigurationManager.
Try this method: ConfigurationManager.OpenMappedExeConfiguration()
There's an example of how to use it in the MSDN article.
Try using an AppSettingsSection instead of a NameValueCollection. Something like this:
var section = (AppSettingsSection)config.GetSection(sectionName);
string results = section.Settings[key].Value;
Source:
http://social.msdn.microsoft.com/Forums/en-US/csharpgeneral/thread/d5079420-40cb-4255-9b3b-f9a41a1f7ad2/
The only way I can get this to work is to manually instantiate the section handler type, pass the raw XML to it, and cast the resulting object.
Seems pretty inefficient, but there you go.
I wrote an extension method to encapsulate this:
public static class ConfigurationSectionExtensions
{
public static T GetAs<T>(this ConfigurationSection section)
{
var sectionInformation = section.SectionInformation;
var sectionHandlerType = Type.GetType(sectionInformation.Type);
if (sectionHandlerType == null)
{
throw new InvalidOperationException(string.Format("Unable to find section handler type '{0}'.", sectionInformation.Type));
}
IConfigurationSectionHandler sectionHandler;
try
{
sectionHandler = (IConfigurationSectionHandler)Activator.CreateInstance(sectionHandlerType);
}
catch (InvalidCastException ex)
{
throw new InvalidOperationException(string.Format("Section handler type '{0}' does not implement IConfigurationSectionHandler.", sectionInformation.Type), ex);
}
var rawXml = sectionInformation.GetRawXml();
if (rawXml == null)
{
return default(T);
}
var xmlDocument = new XmlDocument();
xmlDocument.LoadXml(rawXml);
return (T)sectionHandler.Create(null, null, xmlDocument.DocumentElement);
}
}
The way you would call it in your example is:
var map = new ExeConfigurationFileMap
{
ExeConfigFilename = #"c:\\foo.config"
};
var configuration = ConfigurationManager.OpenMappedExeConfiguration(map, ConfigurationUserLevel.None);
var myParamsSection = configuration.GetSection("MyParams");
var myParamsCollection = myParamsSection.GetAs<NameValueCollection>();
This is an old question, but I use the following class to do the job. It's based on Scott Dorman's blog:
public class NameValueCollectionConfigurationSection : ConfigurationSection
{
private const string COLLECTION_PROP_NAME = "";
public IEnumerable<KeyValuePair<string, string>> GetNameValueItems()
{
foreach ( string key in this.ConfigurationCollection.AllKeys )
{
NameValueConfigurationElement confElement = this.ConfigurationCollection[key];
yield return new KeyValuePair<string, string>
(confElement.Name, confElement.Value);
}
}
[ConfigurationProperty(COLLECTION_PROP_NAME, IsDefaultCollection = true)]
protected NameValueConfigurationCollection ConfCollection
{
get
{
return (NameValueConfigurationCollection) base[COLLECTION_PROP_NAME];
}
}
The usage is straightforward:
Configuration configuration = ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None);
NameValueCollectionConfigurationSection config =
(NameValueCollectionConfigurationSection) configuration.GetSection("MyParams");
NameValueCollection myParamsCollection = new NameValueCollection();
config.GetNameValueItems().ToList().ForEach(kvp => myParamsCollection.Add(kvp));
Here are some examples from this blog mentioned earlier:
<configuration>
<Database>
<add key="ConnectionString" value="data source=.;initial catalog=NorthWind;integrated security=SSPI"/>
</Database>
</configuration>
get values:
NameValueCollection db = (NameValueCollection)ConfigurationSettings.GetConfig("Database");
labelConnection2.Text = db["ConnectionString"];
-
Another example:
<Locations
ImportDirectory="C:\Import\Inbox"
ProcessedDirectory ="C:\Import\Processed"
RejectedDirectory ="C:\Import\Rejected"
/>
get value:
Hashtable loc = (Hashtable)ConfigurationSettings.GetConfig("Locations");
labelImport2.Text = loc["ImportDirectory"].ToString();
labelProcessed2.Text = loc["ProcessedDirectory"].ToString();
Try this;
Credit: https://www.limilabs.com/blog/read-system-net-mailsettings-smtp-settings-web-config
SmtpSection section = (SmtpSection)ConfigurationManager.GetSection("system.net/mailSettings/smtp");
string from = section.From;
string host = section.Network.Host;
int port = section.Network.Port;
bool enableSsl = section.Network.EnableSsl;
string user = section.Network.UserName;
string password = section.Network.Password;
This works like a charm
dynamic configSection = ConfigurationManager.GetSection("MyParams");
var theValue = configSection["FirstParam"];

Categories

Resources