This question already has answers here:
Passing arguments one by one one in console exe by c# code
(3 answers)
Closed 9 years ago.
I am running a console application exe (developed in c#) by my another windows application in c# .
Process p = Process.Start("strExePath");
The console application which i ran expects some user inputs and I want to provide those user inputs by the c# code of windows application which runs the console exe file. How can I achieve this?
You can redirect the Standard Input for a Console application which you start via Process.Start().
To do so, you need to use ProcessStartInfo.RedirectStandardInput.
There's a complete example in the MSDN documentation that I linked.
Also, here's a wrapper class that you could probably adapt to your needs. (The code is somewhat old, so it's not using the latest C# features such as Tasks, but hopefully it will still demonstrate what you need to do.)
Note that to make the I/O redirection work without deadlock when you are redirecting both the input and the output, you must use a separate thread to process the I/O:
/// <summary>
/// Encapsulates an executable program.
/// This class makes it easy to run a console app and have that app's output appear
/// in the parent console's window, and to redirect input and output from/to files.
/// </summary>
/// <remarks>
/// To use this class:
/// (1) Create an instance.
/// (2) Set the ProgramFileName property if a filename wasn't specified in the constructor.
/// (3) Set other properties if required.
/// (4) Call Run().
/// </remarks>
public class Executable
{
#region Constructor
/// <summary>Constructor.</summary>
/// <param name="programFileName">Name of the program file to run.</param>
public Executable(string programFileName)
{
ProgramFileName = programFileName;
_processStartInfo.ErrorDialog = false;
_processStartInfo.CreateNoWindow = false;
_processStartInfo.UseShellExecute = false;
_processStartInfo.RedirectStandardOutput = false;
_processStartInfo.RedirectStandardError = false;
_processStartInfo.RedirectStandardInput = false;
_processStartInfo.WindowStyle = ProcessWindowStyle.Hidden;
_processStartInfo.Arguments = "";
}
/// <summary>Construct with an empty executable name.</summary>
public Executable(): this(string.Empty)
{
}
#endregion // Constructor
#region Public Properties
/// <summary>The filename (full pathname) of the executable.</summary>
public string ProgramFileName
{
get
{
return _processStartInfo.FileName;
}
set
{
_processStartInfo.FileName = value;
}
}
/// <summary> command-line arguments passed to the executable when run.</summary>
public string Arguments
{
get
{
return _processStartInfo.Arguments;
}
set
{
_processStartInfo.Arguments = value;
}
}
/// <summary> The working directory set for the executable when run. </summary>
public string WorkingDirectory
{
get
{
return _processStartInfo.WorkingDirectory;
}
set
{
_processStartInfo.WorkingDirectory = value;
}
}
/// <summary>
/// The file to be used if standard input is redirected,
/// or null or string.Empty to not redirect standard input.
/// </summary>
public string StandardInputFileName
{
set
{
_standardInputFileName = value;
_processStartInfo.RedirectStandardInput = !string.IsNullOrEmpty(value);
}
get
{
return _standardInputFileName;
}
}
/// <summary>
/// The file to be used if standard output is redirected,
/// or null or string.Empty to not redirect standard output.
/// </summary>
public string StandardOutputFileName
{
set
{
_standardOutputFileName = value;
_processStartInfo.RedirectStandardOutput = !string.IsNullOrEmpty(value);
}
get
{
return _standardOutputFileName;
}
}
/// <summary>
/// The file to be used if standard error is redirected,
/// or null or string.Empty to not redirect standard error.
/// </summary>
public string StandardErrorFileName
{
set
{
_standardErrorFileName = value;
_processStartInfo.RedirectStandardError = !string.IsNullOrEmpty(value);
}
get
{
return _standardErrorFileName;
}
}
#endregion // Public Properties
#region Public Methods
/// <summary>Run the executable and wait until the it has terminated.</summary>
/// <returns>The exit code returned from the executable.</returns>
public int Run()
{
Thread standardInputThread = null;
Thread standardOutputThread = null;
Thread standardErrorThread = null;
_standardInput = null;
_standardError = null;
_standardOutput = null;
int exitCode = -1;
try
{
using (Process process = new Process())
{
process.StartInfo = _processStartInfo;
process.Start();
if (process.StartInfo.RedirectStandardInput)
{
_standardInput = process.StandardInput;
standardInputThread = startThread(new ThreadStart(supplyStandardInput), "StandardInput");
}
if (process.StartInfo.RedirectStandardError)
{
_standardError = process.StandardError;
standardErrorThread = startThread(new ThreadStart(writeStandardError), "StandardError");
}
if (process.StartInfo.RedirectStandardOutput)
{
_standardOutput = process.StandardOutput;
standardOutputThread = startThread(new ThreadStart(writeStandardOutput), "StandardOutput");
}
process.WaitForExit();
exitCode = process.ExitCode;
}
}
finally // Ensure that the threads do not persist beyond the process being run
{
if (standardInputThread != null)
standardInputThread.Join();
if (standardOutputThread != null)
standardOutputThread.Join();
if (standardErrorThread != null)
standardErrorThread.Join();
}
return exitCode;
}
#endregion // Public Methods
#region Private Methods
private static Thread startThread(ThreadStart startInfo, string name)
{
Thread t = new Thread(startInfo);
t.IsBackground = true ;
t.Name = name;
t.Start();
return t;
}
/// <summary>Thread which supplies standard input from the appropriate file to the running executable.</summary>
private void supplyStandardInput()
{
// feed text from the file a line at a time into the standard input stream
// NOTE: THERE MAY BE ISSUES RELATED TO CHARACTER ENCODING HERE -- REQUIRES INVESTIGATION
using (StreamReader reader = File.OpenText(_standardInputFileName))
using (StreamWriter writer = _standardInput)
{
writer.AutoFlush = true;
for (;;)
{
string textLine = reader.ReadLine();
if (textLine == null)
break;
writer.WriteLine(textLine);
}
}
}
/// <summary>Thread which outputs standard output from the running executable to the appropriate file.</summary>
private void writeStandardOutput()
{
// NOTE: THERE MAY BE ISSUES RELATED TO CHARACTER ENCODING HERE -- REQUIRES INVESTIGATION
using (StreamWriter writer = File.CreateText(_standardOutputFileName))
using (StreamReader reader = _standardOutput)
{
writer.AutoFlush = true;
for (;;)
{
string textLine = reader.ReadLine();
if (textLine == null)
break;
writer.WriteLine(textLine);
}
}
if (File.Exists(_standardOutputFileName))
{
FileInfo info = new FileInfo(_standardOutputFileName);
// if the error info is empty or just contains eof etc.
if (info.Length < 4)
info.Delete();
}
}
/// <summary>Thread which outputs standard error output from the running executable to the appropriate file.</summary>
private void writeStandardError()
{
// NOTE: THERE MAY BE ISSUES RELATED TO CHARACTER ENCODING HERE -- REQUIRES INVESTIGATION
using (StreamWriter writer = File.CreateText(_standardErrorFileName))
using (StreamReader reader = _standardError)
{
writer.AutoFlush = true;
for (;;)
{
string textLine = reader.ReadLine();
if (textLine == null)
break;
writer.WriteLine(textLine);
}
}
if (File.Exists(_standardErrorFileName))
{
FileInfo info = new FileInfo(_standardErrorFileName);
// if the error info is empty or just contains eof etc.
if (info.Length < 4)
info.Delete();
}
}
#endregion // Private Methods
#region Private Fields
private StreamReader _standardError ;
private StreamReader _standardOutput ;
private StreamWriter _standardInput ;
private string _standardInputFileName;
private string _standardOutputFileName;
private string _standardErrorFileName;
ProcessStartInfo _processStartInfo = new ProcessStartInfo();
#endregion // Private Fields
}
Related
I have started using nLog to log errors on my application recently. However, I am here to see if there is any way to add some information about the currently logged in user on the application to the log layout if a user is currently logged in on the application at the time the exception occurs.
Currently I have the following layout:
<target xsi:type="File" name="errorlogs" fileName="./logs/error-logs/${shortdate}.log"
layout="${longdate}|${event-properties:item=EventId_Id}|${uppercase:${level}}|${logger}|${message} ${exception:format=tostring}|url: ${aspnet-request-url}|action: ${aspnet-mvc-action}" />
However, I wish to add the email of the currently logged in user if the exception happens when there is a logged in user on the application.
My application is running on ASP.Net Core 3.1.
How can I add that to the layout.
Thank you
Maybe not a 100% fit as your question relies on the User logging into your application, and your application running under their user context, but the following works for me in 'normal' application programming...
Create a new public static class. I call mine "Logging".
If your assembly doesn't have it already, add a reference to NLog.
The output format is that used by the freely available Microsoft CMTrace utility. Available here: CMTrace Download link
Add the following:
public static class Logging
{
#region Fields
private static bool _IsSetup = false;
private static Logger _Log = LogManager.GetCurrentClassLogger();
#endregion
#region Private Methods
[MethodImpl(MethodImplOptions.NoInlining)]
private static string GetCurrentMethod()
{
StackTrace st = new StackTrace();
int FrameNumber = 1;
StackFrame sf = st.GetFrame(FrameNumber); // Get the previous stack frame
string MethodName = sf.GetMethod().Name;
string ClassName = sf.GetMethod().ReflectedType.FullName;
while (MethodName == "Log" || MethodName.StartsWith("Write")) // If it's the "Log" or "Write" method calling this, get the method before that one.
{
FrameNumber++;
if (FrameNumber < 6)
{
try
{
MethodName = st.GetFrame(FrameNumber).GetMethod().Name;
ClassName = st.GetFrame(FrameNumber).GetMethod().ReflectedType.FullName;
}
catch
{
}
}
else // Prevent an infinite loop
{
MethodName = "Unknown Method";
ClassName = "Unknown Class";
}
}
return ClassName + "." + MethodName;
}
#endregion
#region Public Methods
/// <summary>
/// Append the specified text to the given TextBox
/// </summary>
/// <param name="Message">The message to append</param>
/// <param name="Control">The TextBox to target</param>
public static void LogToTextbox(string Message, TextBox Control)
{
if (Message.Length > 0)
{
Control.AppendText(Message + Environment.NewLine);
Control.Refresh();
Application.DoEvents();
}
}
/// <summary>
/// Setup Logging
/// </summary>
/// <param name="Overwrite">If set to true, any existing file will be over-written</param>
public static void Setup(bool Overwrite = false)
{
LoggingConfiguration Config = new LoggingConfiguration();
FileTarget File = new FileTarget();
Config.AddTarget("File", File);
File.Layout = "${message}";
File.FileName = System.IO.Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData), Application.CompanyName, System.Diagnostics.Process.GetCurrentProcess().ProcessName) + ".log";
File.AutoFlush = true;
File.KeepFileOpen = false;
File.ArchiveFileName = System.IO.Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData), Application.CompanyName, System.Diagnostics.Process.GetCurrentProcess().ProcessName) + "_{#}.log";
File.ArchiveNumbering = ArchiveNumberingMode.Rolling;
File.ArchiveEvery = FileArchivePeriod.Day;
File.MaxArchiveDays = 31;
if (Overwrite)
{
File.DeleteOldFileOnStartup = true;
}
// Create rules
LoggingRule Rule1 = new LoggingRule("*", LogLevel.Trace, File);
// Apply rules
Config.LoggingRules.Add(Rule1);
// Activate logging
LogManager.Configuration = Config;
// Cleanup
_IsSetup = true;
}
/// <summary>
/// Write the specified message type and string to the logfile, located at %PROGRAMDATA/[Application.CompanyName]
/// </summary>
/// <param name="Level">The level of message to write</param>
/// <param name="Message">The message to write</param>
public static void Write(LogLevel Level, string Message)
{
string Severity;
string OutputMessage;
DateTime UtcNow = DateTime.UtcNow;
DateTime Now = DateTime.Now;
string UtcDate = UtcNow.ToString("MM-dd-yyyy");
string UtcTime = UtcNow.ToString("HH:mm:ss.") + UtcNow.Millisecond;
string Date = Now.ToString("dd-MM-yyyy");
string Time = Now.ToString("HH:mm:ss.") + UtcNow.Millisecond;
string TZOffset = TimeZoneInfo.Local.GetUtcOffset(Now).TotalHours.ToString("+000");
if (!_IsSetup)
{
Setup();
}
Trace.WriteLine(Message);
Message = $"{Date} {Time}: {Message}";
switch (Level.Name)
{
default:
Severity = "0";
break;
case "Info":
Severity = "1";
break;
case "Warn":
Severity = "2";
break;
case "Error":
Severity = "3";
break;
}
// https://adamtheautomator.com/building-logs-for-cmtrace-powershell/
OutputMessage = $"<![LOG[{Message}]LOG]!><time=\"{UtcTime}{TZOffset}\" date=\"{UtcDate}\" component=\"{GetCurrentMethod()}\" context=\"{Environment.UserName}\" type=\"{Severity}\" thread=\"{Thread.CurrentThread.ManagedThreadId}\" file=\"{System.Diagnostics.Process.GetCurrentProcess().ProcessName}\">";
// The following can be used as a catch-all
//try
//{
_Log.Log(Level, OutputMessage);
//}
//catch (Exception e)
//{
// // If we cannot write to the log file, write to the EventLog instead.
// using (EventLog eventLog = new EventLog("Application"))
// {
// string OutputFolderName = System.IO.Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData), Application.CompanyName);
// string ExecutableName = System.Diagnostics.Process.GetCurrentProcess().ProcessName;
// eventLog.Source = "Application";
// eventLog.WriteEntry($"Failed to write to application logfile (in {OutputFolderName}) for {ExecutableName}. The error was: {e.Message}", EventLogEntryType.Error, 101, 1);
// }
//}
}
/// <summary>
/// Write a error message to the logfile, located at %PROGRAMDATA/[Application.CompanyName]
/// </summary>
/// <param name="Message">The message to write</param>
public static void WriteError(string Message)
{
Write(LogLevel.Error, Message);
}
/// <summary>
/// Write an informational message to the logfile, located at %PROGRAMDATA/[Application.CompanyName]
/// </summary>
/// <param name="Message">The message to write</param>
public static void WriteInfo(string Message)
{
Write(LogLevel.Info, Message);
}
/// <summary>
/// Write a warning message to the logfile, located at %PROGRAMDATA/[Application.CompanyName]
/// </summary>
/// <param name="Message">The message to write</param>
public static void WriteWarning(string Message)
{
Write(LogLevel.Warn, Message);
}
#endregion
}
Usage:
Logging.Setup();
Logging.WriteInfo("Application startup");
Logging.WriteError($"{DestinationFilename}: Cannot overwrite file. User advised to delete file manually. The error was: {ex.Message}");
Example output:
<![LOG[20-11-2020 13:22:48.626: Application startup]LOG]!><time="05:22:48.626+008" date="11-20-2020" component="Bitberry.Elda.GetLatest.frmMain..ctor" context="DaveR" type="1" thread="1" file="GetLatest">
Specifically, the 'context="USERNAME"' portion is what you have asked for.
Hello so I'm doing a unit test in c# with Mono. I'm testing a async method that uses delegates to return the response of a web service.
So here is my test:
[Test]
public void TestCreateUser()
{
Debug.Log("TestCreateUser");
Vizzario.Instance.OnCreateUserSuccess += delegate(CreateUserResponse x)
{
Debug.Log("TestCreateUser: " + x.Response);
UnityEngine.Assertions.Assert.IsNotNull(x.TokenResponse.AuthToken);
UnityEngine.Assertions.Assert.AreEqual(BaseResponse.ResponseEnum.SUCCESS, x.Response);
// Signal that work is finished.
bool s = autoResetEvent.Set();
Debug.Log(s);
};
// Does the request
InitData.NewUserData.Email = InitData.GetRandomEmail();
Vizzario.Instance.CreateUser(InitData.NewUserData);
// Wait for work method to signal.
bool a = autoResetEvent.WaitOne(TimeSpan.FromSeconds(5), true);
Debug.Log(a);
}
So when I run this test the result from WaitOne executes before executing the body of my MyClass.Instance.OnCreateUserSuccess and it prints false.
So it prints something like: 1. false, 2. TestCreateUser: Success
When it should print something like: 1. TestCreateUser: Success, 2. true
Why is the waitone not working, I try increasing autoResetEvent.WaitOne(TimeSpan.FromSeconds(5), true); to autoResetEvent.WaitOne(TimeSpan.FromSeconds(60), true); but I got the same results I know as a fact that the CreateUser method doesn't take more than 1 or 2 seconds so if I try autoResetEvent.WaitOne(); my test hangs and I have to restart the ide.
Any ideas.
Thanks in advance!
As requested I'm adding the method that is called in my vizzario class:
public delegate void CreateUserCallback(CreateUserResponse response);
public event CreateUserCallback OnCreateUserSuccess;
public event CreateUserCallback OnCreateUserError;
public void CreateUser(NewUserData data)
{
CreateUserResponse response = new CreateUserResponse();
this.DefApi.UserPost(data, ApiKey, ToolkitVersion, dryRun)
.Then(x =>
{
response.TokenResponse = x;
response.Response = BaseResponse.ResponseEnum.SUCCESS;
DoCreateUserNext(response);
})
.Catch(e =>
{
response.Response = BaseResponse.ResponseEnum.ERROR;
});
}
/// <summary>
/// Return callback in case of an exception inside the sdk.
/// </summary>
/// <param name="e"></param>
private void DoCreateUserError(Exception e)
{
if (OnCreateUserError != null)
{
CreateUserResponse response = new CreateUserResponse();
response.Response = BaseResponse.ResponseEnum.ERROR;
response.message = e.Message;
OnCreateUserError(response);
}
}
/// <summary>
/// Return callback from the sdk to the developer.
/// </summary>
/// <param name="response"></param>
private void DoCreateUserNext(CreateUserResponse response)
{
if (OnCreateUserSuccess != null)
{
if (response.Response == BaseResponse.ResponseEnum.SUCCESS)
{
OnCreateUserSuccess(response);
}
}
if (OnCreateUserError != null)
{
if (response.Response == BaseResponse.ResponseEnum.ERROR)
{
OnCreateUserError(response);
}
}
}
I have an application splittet into two projects: a web application and a class library. The Startup is only in the web application:
var builder = new ConfigurationBuilder()
.AddJsonFile("appsettings.json")
I wanna have my appsettings.json in that class library and being loaded from there. Is that possible? How can I do that?
The best solution I have found requires creating a new IFileProvider and IFileInfo, and then embedding the JSON settings files in your assembly.
The solution reuses the existing AddJsonFile logic. This way you only need to tell the configuration system how and where to locate the JSON file, not how to parse it.
The solution is compatible with .NET Core 1.0.
Usage:
public class Startup
{
private readonly AppSettings _appSettings;
public Startup(IHostingEnvironment env)
{
Assembly assembly = GetType().GetTypeInfo().Assembly;
new ConfigurationBuilder()
.AddEmbeddedJsonFile(assembly, "appsettings.json")
.AddEmbeddedJsonFile(assembly, $"appsettings.{env.EnvironmentName.ToLower()}.json")
.Build();
}
...
}
Embed the files by updating the project.json for the class library:
...
"buildOptions": {
"embed": [
"appsettings.json",
"appsettings.development.json",
"appsettings.production.json"
]
}
...
IEmbeddedFileInfo implementation:
public class EmbeddedFileInfo : IFileInfo
{
private readonly Stream _fileStream;
public EmbeddedFileInfo(string name, Stream fileStream)
{
if (name == null) throw new ArgumentNullException(nameof(name));
if (fileStream == null) throw new ArgumentNullException(nameof(fileStream));
_fileStream = fileStream;
Exists = true;
IsDirectory = false;
Length = fileStream.Length;
Name = name;
PhysicalPath = name;
LastModified = DateTimeOffset.Now;
}
public Stream CreateReadStream()
{
return _fileStream;
}
public bool Exists { get; }
public bool IsDirectory { get; }
public long Length { get; }
public string Name { get; }
public string PhysicalPath { get; }
public DateTimeOffset LastModified { get; }
}
IFileInfo implementation:
public class EmbeddedFileProvider : IFileProvider
{
private readonly Assembly _assembly;
public EmbeddedFileProvider(Assembly assembly)
{
if (assembly == null) throw new ArgumentNullException(nameof(assembly));
_assembly = assembly;
}
public IFileInfo GetFileInfo(string subpath)
{
string fullFileName = $"{_assembly.GetName().Name}.{subpath}";
bool isFileEmbedded = _assembly.GetManifestResourceNames().Contains(fullFileName);
return isFileEmbedded
? new EmbeddedFileInfo(subpath, _assembly.GetManifestResourceStream(fullFileName))
: (IFileInfo) new NotFoundFileInfo(subpath);
}
public IDirectoryContents GetDirectoryContents(string subpath)
{
throw new NotImplementedException();
}
public IChangeToken Watch(string filter)
{
throw new NotImplementedException();
}
}
Create the easy to use AddEmbeddedJsonFile extension method.
public static class ConfigurationBuilderExtensions
{
public static IConfigurationBuilder AddEmbeddedJsonFile(this IConfigurationBuilder cb,
Assembly assembly, string name, bool optional = false)
{
// reload on change is not supported, always pass in false
return cb.AddJsonFile(new EmbeddedFileProvider(assembly), name, optional, false);
}
}
Yes you could implement IConfigurationProvider
There is a base class ConfigurationProvider that you can inherit from then override all the virtual methods.
You can also see how the JsonConfigurationProvider is implemented.
So I guess your implementation could use the Json provider code internally against embedded json files.
Then you would also want to implement ConfigurationBuilder extension to register your provider similar as the code for using json config.
Someone else can correct me, but I don't think what you are looking for exists.
App Configs and AppSettings files are read at runtime by the application that is running.
The Class Library cannot see any AppSettings specific to itself, because when it runs at run time, it is in the folder of the running application.
The only potential way I can see for you to get your class library contain the json file, is to have the json file as an embedded resource.
Eg: In the solution, select the json file, and set it to Embedded Resource instead of 'content'.
The problem becomes getting the embedded config file out of your assembly, and then loaded.
AddJsonFile accepts a path to the json file.
You could however extract the Json file to a temp directory, then load from there.
static byte[] StreamToBytes(Stream input)
{
int capacity = input.CanSeek ? (int)input.Length : 0;
using (MemoryStream output = new MemoryStream(capacity))
{
int readLength;
byte[] buffer = new byte[capacity/*4096*/]; //An array of bytes
do
{
readLength = input.Read(buffer, 0, buffer.Length); //Read the memory data, into the buffer
output.Write(buffer, 0, readLength);
}
while (readLength != 0); //Do all this while the readLength is not 0
return output.ToArray(); //When finished, return the finished MemoryStream object as an array.
}
}
Assembly yourAssembly = Assembly.GetAssembly(typeof(MyTypeWithinAssembly));
using (Stream input = yourAssembly.GetManifestResourceStream("NameSpace.Resources.Config.json")) // Acquire the dll from local memory/resources.
{
byte[] byteData = StreamToBytes(input);
System.IO.File.WriteAllBytes(Environment.GetFolderPath(System.Environment.SpecialFolder.LocalApplicationData)+"//Config.json",new byte[]{});
}
var builder = new ConfigurationBuilder()
.AddJsonFile(Environment.GetFolderPath(System.Environment.SpecialFolder.LocalApplicationData)+"//Config.json");
You should in theory be able to specify a type from the class library, in order to help the c# code target that class library specifically. Then you just need to provide the namespace and path to the embedded json file.
Here is my solution, thanks Baaleos and Joe for your advices.
project.json
"resource": [
"appsettings.json"
]
startup.cs
var builder = new ConfigurationBuilder()
.Add(new SettingsConfigurationProvider("appsettings.json"))
.AddEnvironmentVariables();
this.Configuration = builder.Build();
namespace ClassLibrary
{
using Microsoft.Extensions.Configuration;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
/// <summary>
/// A JSON file based <see cref="ConfigurationProvider"/> for embedded resources.
/// </summary>
public class SettingsConfigurationProvider : ConfigurationProvider
{
/// <summary>
/// Initializes a new instance of <see cref="SettingsConfigurationProvider"/>.
/// </summary>
/// <param name="name">Name of the JSON configuration file.</param>
/// <param name="optional">Determines if the configuration is optional.</param>
public SettingsConfigurationProvider(string name)
: this(name, false)
{
}
/// <summary>
/// Initializes a new instance of <see cref="SettingsConfigurationProvider"/>.
/// </summary>
/// <param name="name">Name of the JSON configuration file.</param>
/// <param name="optional">Determines if the configuration is optional.</param>
public SettingsConfigurationProvider(string name, bool optional)
{
if (string.IsNullOrEmpty(name))
{
throw new ArgumentException("Name must be a non-empty string.", nameof(name));
}
this.Optional = optional;
this.Name = name;
}
/// <summary>
/// Gets a value that determines if this instance of <see cref="SettingsConfigurationProvider"/> is optional.
/// </summary>
public bool Optional { get; }
/// <summary>
/// The name of the file backing this instance of <see cref="SettingsConfigurationProvider"/>.
/// </summary>
public string Name { get; }
/// <summary>
/// Loads the contents of the embedded resource with name <see cref="Path"/>.
/// </summary>
/// <exception cref="FileNotFoundException">If <see cref="Optional"/> is <c>false</c> and a
/// resource does not exist with name <see cref="Path"/>.</exception>
public override void Load()
{
Assembly assembly = Assembly.GetAssembly(typeof(SettingsConfigurationProvider));
var resourceName = $"{assembly.GetName().Name}.{this.Name}";
var resources = assembly.GetManifestResourceNames();
if (!resources.Contains(resourceName))
{
if (Optional)
{
Data = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
}
else
{
throw new FileNotFoundException($"The configuration file with name '{this.Name}' was not found and is not optional.");
}
}
else
{
using (Stream settingsStream = assembly.GetManifestResourceStream(resourceName))
{
Load(settingsStream);
}
}
}
internal void Load(Stream stream)
{
JsonConfigurationFileParser parser = new JsonConfigurationFileParser();
try
{
Data = parser.Parse(stream);
}
catch (JsonReaderException e)
{
string errorLine = string.Empty;
if (stream.CanSeek)
{
stream.Seek(0, SeekOrigin.Begin);
IEnumerable<string> fileContent;
using (var streamReader = new StreamReader(stream))
{
fileContent = ReadLines(streamReader);
errorLine = RetrieveErrorContext(e, fileContent);
}
}
throw new FormatException($"Could not parse the JSON file. Error on line number '{e.LineNumber}': '{e}'.");
}
}
private static string RetrieveErrorContext(JsonReaderException e, IEnumerable<string> fileContent)
{
string errorLine;
if (e.LineNumber >= 2)
{
var errorContext = fileContent.Skip(e.LineNumber - 2).Take(2).ToList();
errorLine = errorContext[0].Trim() + Environment.NewLine + errorContext[1].Trim();
}
else
{
var possibleLineContent = fileContent.Skip(e.LineNumber - 1).FirstOrDefault();
errorLine = possibleLineContent ?? string.Empty;
}
return errorLine;
}
private static IEnumerable<string> ReadLines(StreamReader streamReader)
{
string line;
do
{
line = streamReader.ReadLine();
yield return line;
} while (line != null);
}
}
}
You need also the JsonConfigurationFileParser:
namespace ClassLibrary
{
using Microsoft.Extensions.Configuration;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
internal class JsonConfigurationFileParser
{
private readonly IDictionary<string, string> _data = new SortedDictionary<string, string>(StringComparer.OrdinalIgnoreCase);
private readonly Stack<string> _context = new Stack<string>();
private string _currentPath;
private JsonTextReader _reader;
public IDictionary<string, string> Parse(Stream input)
{
_data.Clear();
_reader = new JsonTextReader(new StreamReader(input));
_reader.DateParseHandling = DateParseHandling.None;
var jsonConfig = JObject.Load(_reader);
VisitJObject(jsonConfig);
return _data;
}
private void VisitJObject(JObject jObject)
{
foreach (var property in jObject.Properties())
{
EnterContext(property.Name);
VisitProperty(property);
ExitContext();
}
}
private void VisitProperty(JProperty property)
{
VisitToken(property.Value);
}
private void VisitToken(JToken token)
{
switch (token.Type)
{
case JTokenType.Object:
VisitJObject(token.Value<JObject>());
break;
case JTokenType.Array:
VisitArray(token.Value<JArray>());
break;
case JTokenType.Integer:
case JTokenType.Float:
case JTokenType.String:
case JTokenType.Boolean:
case JTokenType.Bytes:
case JTokenType.Raw:
case JTokenType.Null:
VisitPrimitive(token);
break;
default:
throw new FormatException($#"
Unsupported JSON token '{_reader.TokenType}' was found.
Path '{_reader.Path}',
line {_reader.LineNumber}
position {_reader.LinePosition}.");
}
}
private void VisitArray(JArray array)
{
for (int index = 0; index < array.Count; index++)
{
EnterContext(index.ToString());
VisitToken(array[index]);
ExitContext();
}
}
private void VisitPrimitive(JToken data)
{
var key = _currentPath;
if (_data.ContainsKey(key))
{
throw new FormatException($"A duplicate key '{key}' was found.");
}
_data[key] = data.ToString();
}
private void EnterContext(string context)
{
_context.Push(context);
_currentPath = string.Join(Constants.KeyDelimiter, _context.Reverse());
}
private void ExitContext()
{
_context.Pop();
_currentPath = string.Join(Constants.KeyDelimiter, _context.Reverse());
}
}
}
I am trying to Send Error reports with hockeyapp without having to let the whole app crash and burn. I dont think the HockeyApp.WPF library has this capability, so I started to mess around with implementing my own CrashHandler.
This quickly got confusing and very hackey. Does anyone have any code examples for this? At my current rate I will end up reproducing half of the HockeyApp Library, so I would appreciate some help.
I am not posting my code because I don't think it will help and its too much.
Edit: now I will post a shortened version of code that doesnt seem to work:
private static void HandleException(Exception e) {
try {
string crashID = Guid.NewGuid().ToString();
String filename = String.Format("{0}{1}.log", CrashFilePrefix, crashID);
CrashLogInformation logInfo = new CrashLogInformation() {
PackageName = Application.Current.GetType().Namespace,
Version = ((HockeyClient)HockeyClient.Current).VersionInfo,
OperatingSystem = Environment.OSVersion.Platform.ToString(),
Windows = Environment.OSVersion.Version.ToString() + Environment.OSVersion.ServicePack,
Manufacturer = "",
Model = ""
};
ICrashData crash = ((HockeyClient)HockeyClient.Current).CreateCrashData(e);
using (FileStream stream = File.Create(Path.Combine(GetPathToHockeyCrashes(), filename))) {
crash.Serialize(stream);
stream.Flush();
}
}
catch (Exception ex) {
((HockeyClient)HockeyClient.Current).HandleInternalUnhandledException(ex);
}
}
private static string GetPathToHockeyCrashes() {
string path = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData);
if (!path.EndsWith("\\")) { path += "\\"; }
path += "HockeyCrashes\\";
if (!Directory.Exists(path)) { Directory.CreateDirectory(path); }
return path;
}
private struct CrashLogInformation {
/// <summary>
/// name of app package
/// </summary>
public string PackageName;
/// <summary>
/// version of app
/// </summary>
public string Version;
/// <summary>
/// os
/// </summary>
public string OperatingSystem;
/// <summary>
/// device manufacturer
/// </summary>
public string Manufacturer;
/// <summary>
/// device model
/// </summary>
public string Model;
/// <summary>
/// product id of app
/// </summary>
public string ProductID;
/// <summary>
/// windows phone version
/// </summary>
public string WindowsPhone;
/// <summary>
/// windows version
/// </summary>
public string Windows;
}
I was able to make a post after formatting the logs as described in the documentation for the crashes/upload endpoint(http://support.hockeyapp.net/kb/api/api-crashes#-u-post-api-2-apps-app_id-crashes-upload-u-).
Although I ended up hitting "crashes/", which from my understanding is different from crashes/upload(Therefore this is a solution that is hitting an undocumented endpoint).
private static readonly string HOCKEYUPLOADURL = #"https://rink.hockeyapp.net/api/2/apps/{0}/crashes/";
private static async Task SendDataAsync(String log, String userID, String contact, String description) {
string rawData = "";
rawData += "raw=" + Uri.EscapeDataString(log);
if (userID != null) {
rawData += "&userID=" + Uri.EscapeDataString(userID);
}
if (contact != null) {
rawData += "&contact=" + Uri.EscapeDataString(contact);
}
if (description != null) {
rawData += "&description=" + Uri.EscapeDataString(description);
}
WebRequest request = WebRequest.Create(new Uri(String.Format(HOCKEYUPLOADURL, HOCKEYAPPID)));
request.Method = "POST";
request.ContentType = "application/x-www-form-urlencoded";
using (Stream stream = await request.GetRequestStreamAsync()) {
byte[] byteArray = Encoding.UTF8.GetBytes(rawData);
stream.Write(byteArray, 0, rawData.Length);
stream.Flush();
}
try {
using (WebResponse response = await request.GetResponseAsync()) { }
}
catch (WebException e) {
WriteLocalLog(e, "HockeyApp SendDataAsync failed");
}
}
I'm building an app in .NET and C#, and I'd like to cache some of the results by using attributes/annotations instead of explicit code in the method.
I'd like a method signature that looks a bit like this:
[Cache, timeToLive=60]
String getName(string id, string location)
It should make a hash based on the inputs, and use that as the key for the result.
Naturally, there'd be some config file telling it how to actually put in memcached, local dictionary or something.
Do you know of such a framework?
I'd even be interested in one for Java as well
With CacheHandler in Microsoft Enterprise Library you can easily achieve this.
For instance:
[CacheHandler(0, 30, 0)]
public Object GetData(Object input)
{
}
would make all calls to that method cached for 30 minutes. All invocations gets a unique cache-key based on the input data and method name so if you call the method twice with different input it doesn't get cached but if you call it >1 times within the timout interval with the same input then the method only gets executed once.
I've added some extra features to Microsoft's code:
My modified version looks like this:
using System;
using System.Diagnostics;
using System.IO;
using System.Reflection;
using System.Runtime.Remoting.Contexts;
using System.Text;
using System.Web;
using System.Web.Caching;
using System.Web.UI;
using Microsoft.Practices.EnterpriseLibrary.Common.Configuration;
using Microsoft.Practices.Unity.InterceptionExtension;
namespace Middleware.Cache
{
/// <summary>
/// An <see cref="ICallHandler"/> that implements caching of the return values of
/// methods. This handler stores the return value in the ASP.NET cache or the Items object of the current request.
/// </summary>
[ConfigurationElementType(typeof (CacheHandler)), Synchronization]
public class CacheHandler : ICallHandler
{
/// <summary>
/// The default expiration time for the cached entries: 5 minutes
/// </summary>
public static readonly TimeSpan DefaultExpirationTime = new TimeSpan(0, 5, 0);
private readonly object cachedData;
private readonly DefaultCacheKeyGenerator keyGenerator;
private readonly bool storeOnlyForThisRequest = true;
private TimeSpan expirationTime;
private GetNextHandlerDelegate getNext;
private IMethodInvocation input;
public CacheHandler(TimeSpan expirationTime, bool storeOnlyForThisRequest)
{
keyGenerator = new DefaultCacheKeyGenerator();
this.expirationTime = expirationTime;
this.storeOnlyForThisRequest = storeOnlyForThisRequest;
}
/// <summary>
/// This constructor is used when we wrap cached data in a CacheHandler so that
/// we can reload the object after it has been removed from the cache.
/// </summary>
/// <param name="expirationTime"></param>
/// <param name="storeOnlyForThisRequest"></param>
/// <param name="input"></param>
/// <param name="getNext"></param>
/// <param name="cachedData"></param>
public CacheHandler(TimeSpan expirationTime, bool storeOnlyForThisRequest,
IMethodInvocation input, GetNextHandlerDelegate getNext,
object cachedData)
: this(expirationTime, storeOnlyForThisRequest)
{
this.input = input;
this.getNext = getNext;
this.cachedData = cachedData;
}
/// <summary>
/// Gets or sets the expiration time for cache data.
/// </summary>
/// <value>The expiration time.</value>
public TimeSpan ExpirationTime
{
get { return expirationTime; }
set { expirationTime = value; }
}
#region ICallHandler Members
/// <summary>
/// Implements the caching behavior of this handler.
/// </summary>
/// <param name="input"><see cref="IMethodInvocation"/> object describing the current call.</param>
/// <param name="getNext">delegate used to get the next handler in the current pipeline.</param>
/// <returns>Return value from target method, or cached result if previous inputs have been seen.</returns>
public IMethodReturn Invoke(IMethodInvocation input, GetNextHandlerDelegate getNext)
{
lock (input.MethodBase)
{
this.input = input;
this.getNext = getNext;
return loadUsingCache();
}
}
public int Order
{
get { return 0; }
set { }
}
#endregion
private IMethodReturn loadUsingCache()
{
//We need to synchronize calls to the CacheHandler on method level
//to prevent duplicate calls to methods that could be cached.
lock (input.MethodBase)
{
if (TargetMethodReturnsVoid(input) || HttpContext.Current == null)
{
return getNext()(input, getNext);
}
var inputs = new object[input.Inputs.Count];
for (int i = 0; i < inputs.Length; ++i)
{
inputs[i] = input.Inputs[i];
}
string cacheKey = keyGenerator.CreateCacheKey(input.MethodBase, inputs);
object cachedResult = getCachedResult(cacheKey);
if (cachedResult == null)
{
var stopWatch = Stopwatch.StartNew();
var realReturn = getNext()(input, getNext);
stopWatch.Stop();
if (realReturn.Exception == null && realReturn.ReturnValue != null)
{
AddToCache(cacheKey, realReturn.ReturnValue);
}
return realReturn;
}
var cachedReturn = input.CreateMethodReturn(cachedResult, input.Arguments);
return cachedReturn;
}
}
private object getCachedResult(string cacheKey)
{
//When the method uses input that is not serializable
//we cannot create a cache key and can therefore not
//cache the data.
if (cacheKey == null)
{
return null;
}
object cachedValue = !storeOnlyForThisRequest ? HttpRuntime.Cache.Get(cacheKey) : HttpContext.Current.Items[cacheKey];
var cachedValueCast = cachedValue as CacheHandler;
if (cachedValueCast != null)
{
//This is an object that is reloaded when it is being removed.
//It is therefore wrapped in a CacheHandler-object and we must
//unwrap it before returning it.
return cachedValueCast.cachedData;
}
return cachedValue;
}
private static bool TargetMethodReturnsVoid(IMethodInvocation input)
{
var targetMethod = input.MethodBase as MethodInfo;
return targetMethod != null && targetMethod.ReturnType == typeof (void);
}
private void AddToCache(string key, object valueToCache)
{
if (key == null)
{
//When the method uses input that is not serializable
//we cannot create a cache key and can therefore not
//cache the data.
return;
}
if (!storeOnlyForThisRequest)
{
HttpRuntime.Cache.Insert(
key,
valueToCache,
null,
System.Web.Caching.Cache.NoAbsoluteExpiration,
expirationTime,
CacheItemPriority.Normal, null);
}
else
{
HttpContext.Current.Items[key] = valueToCache;
}
}
}
/// <summary>
/// This interface describes classes that can be used to generate cache key strings
/// for the <see cref="CacheHandler"/>.
/// </summary>
public interface ICacheKeyGenerator
{
/// <summary>
/// Creates a cache key for the given method and set of input arguments.
/// </summary>
/// <param name="method">Method being called.</param>
/// <param name="inputs">Input arguments.</param>
/// <returns>A (hopefully) unique string to be used as a cache key.</returns>
string CreateCacheKey(MethodBase method, object[] inputs);
}
/// <summary>
/// The default <see cref="ICacheKeyGenerator"/> used by the <see cref="CacheHandler"/>.
/// </summary>
public class DefaultCacheKeyGenerator : ICacheKeyGenerator
{
private readonly LosFormatter serializer = new LosFormatter(false, "");
#region ICacheKeyGenerator Members
/// <summary>
/// Create a cache key for the given method and set of input arguments.
/// </summary>
/// <param name="method">Method being called.</param>
/// <param name="inputs">Input arguments.</param>
/// <returns>A (hopefully) unique string to be used as a cache key.</returns>
public string CreateCacheKey(MethodBase method, params object[] inputs)
{
try
{
var sb = new StringBuilder();
if (method.DeclaringType != null)
{
sb.Append(method.DeclaringType.FullName);
}
sb.Append(':');
sb.Append(method.Name);
TextWriter writer = new StringWriter(sb);
if (inputs != null)
{
foreach (var input in inputs)
{
sb.Append(':');
if (input != null)
{
//Diffrerent instances of DateTime which represents the same value
//sometimes serialize differently due to some internal variables which are different.
//We therefore serialize it using Ticks instead. instead.
var inputDateTime = input as DateTime?;
if (inputDateTime.HasValue)
{
sb.Append(inputDateTime.Value.Ticks);
}
else
{
//Serialize the input and write it to the key StringBuilder.
serializer.Serialize(writer, input);
}
}
}
}
return sb.ToString();
}
catch
{
//Something went wrong when generating the key (probably an input-value was not serializble.
//Return a null key.
return null;
}
}
#endregion
}
}
Microsoft deserves most credit for this code. We've only added stuff like caching at request level instead of across requests (more useful than you might think) and fixed some bugs (e.g. equal DateTime-objects serializing to different values).
To do exactly what you are describing, i.e. writing
public class MyClass {
[Cache, timeToLive=60]
string getName(string id, string location){
return ExpensiveCall(id, location);
}
}
// ...
MyClass c = new MyClass();
string name = c.getName("id", "location");
string name_again = c.getName("id", "location");
and having only one invocation of the expensive call and without needing to wrap the class with some other code (f.x. CacheHandler<MyClass> c = new CacheHandler<MyClass>(new MyClass());) you need to look into an Aspect Oriented Programming framework. Those usually work by rewriting the byte-code, so you need to add another step to your compilation process - but you gain a lot of power in the process. There are many AOP-frameworks, but PostSharp for .NET and AspectJ are among the most popular. You can easily Google how to use those to add the caching-aspect you want.