A bit of background:
I am currently working on an application that allows novice computer users to test their ping without having to go into the command prompt.
My application works, but I would very much like to take the application to the next level and feed in default form values from a locally stored .INI file.
I can give people the existing code, but I stress that this application works - I am just interested in advancing the code so I can read in default form values.
using System;
using System.Collections.Generic;
using System.Net;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Net.NetworkInformation;
namespace Ping_Application
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void pingButton_Click(object sender, EventArgs e)
{
if (pingAddressTextBox.Text != "")
{
DataTable resultsList = new DataTable();
resultsList.Columns.Add("Time", typeof(int));
resultsList.Columns.Add("Status", typeof(string));
for (int indexVariable = 1; indexVariable <= timesToPing.Value; indexVariable++)
{
string stat = "";
Ping pinger = new Ping();
PingReply reply = pinger.Send(pingAddressTextBox.Text);
if (reply.Status.ToString() != "Success")
stat = "Failed";
else
stat = reply.RoundtripTime.ToString();
pinger.Dispose();
resultsList.Rows.Add(Convert.ToInt32(reply.RoundtripTime), reply.Status.ToString());
}
resultsGrid.DataSource = resultsList;
minPing.Text = resultsList.Compute("MIN(time)", "").ToString();
maxPing.Text = resultsList.Compute("MAX(time)", "").ToString();
avgPing.Text = resultsList.Compute("AVG(time)", "").ToString();
}
else
{
MessageBox.Show("You are required to enter an address.");
}
}
private void Form1_Load(object sender, EventArgs e)
{
}
}
}
I am uncertain how to go about it? Where would the default.ini file be stored for my application?
Also any comments on the existing code are welcomed.
If anyone can help I would be grateful.
Many Thanks,
J
You can store your default values in ini file (i.e config file), this default file will be stored in your system D or C folder...
and from that file you can get those default values from the ini file by the following method
/// <summary>
/// This will read config.ini file and return the specific value
/// </summary>
/// <param name="MainSection">Main catergory name</param>
/// <param name="key">name of the key in main catergory</param>
/// <param name="defaultValue">if key is not in the section, then default value</param>
/// <returns></returns>
public static string getIniValue(string MainSection, string key, string defaultValue)
{
IniFile inif = new IniFile(AppDataPath() + #"\config.ini");
string value = "";
value = (inif.IniReadValue(MainSection, key, defaultValue));
return value;
}
public static string AppDataPath()
{
gCommonAppDataPath = #"c:\" + gCompanyName + #"\" + gProductName; // your config file location path
return gCommonAppDataPath;
}
make a class like this INifile.cs and place the below code in ini.cs
public class IniFile
{
public string path;
[DllImport("kernel32")]
private static extern long WritePrivateProfileString(string section,string key,string val,string filePath);
[DllImport("kernel32")]
private static extern int GetPrivateProfileString(string section,string key,string def,StringBuilder retVal,int size,string filePath);
/// <summary>
/// INIFile Constructor.
/// </summary>
/// <param name="INIPath"></param>
public IniFile(string INIPath)
{
path = INIPath;
}
/// <summary>
/// Write Data to the INI File
/// </summary>
/// <param name="Section"></param>
/// Section name
/// <param name="Key"></param>
/// Key Name
/// <param name="Value"></param>
/// Value Name
public void IniWriteValue(string Section,string Key,string Value)
{
WritePrivateProfileString(Section,Key,Value,this.path);
}
/// <summary>
/// Read Data Value From the Ini File
/// </summary>
/// <param name="Section"></param>
/// <param name="Key"></param>
/// <param name="Path"></param>
/// <returns></returns>
public string IniReadValue(string Section,string Key,string Default)
{
StringBuilder temp = new StringBuilder(255);
int i = GetPrivateProfileString(Section,Key,Default,temp,255,this.path);
return temp.ToString();
}
public void IniWriteString(string Section, string Key, string Value)
{
WritePrivateProfileString(Section, Key, Value, this.path);
}
public string IniReadString(string Section, string Key, string Default)
{
StringBuilder temp = new StringBuilder(255);
int i = GetPrivateProfileString(Section, Key, Default, temp, 255, this.path);
return temp.ToString();
}
}
and the values in config file look like this ....
[System]
GroupCode=xx
SiteCode=1234
MemberPrefix=xxx
AutoStart=no
EnablePosButton=yes....
you can get this values like by using
string a = getIniValue("System", "Sitecode", "");
you will get the value like this 1234
pls let me know if this is unclear to understand
i hope it will helps you......
If you are using Visual Studio 2005/2008 or 2010, using INI might not be a good choice. Instead you can use the off-the-shelf tools provided by Visual Studio, by clicking:
Project> Properties > Settings Tab
where you can add user settings and bind it to your GUI form. Visual Studio takes care of most of the stuff for you, and when you want to reference that variable, use the below syntax:
string LocalString=Properties.Settings.Default.YourSettings;
Moreover, a single call helps to save all the staff to archives.
Properties.Settings.Default.Save();
For even more details, please refer to the book Windows Form 2.0 Data Binding.
Related
First off, I'm fairly new to WCF so please keep that in mind when responding.
The problem is I created a WCF library (AppCommWcf) that is shared between the client and the server. When I write a Console application using the AppCommWcf library and call it, it works fine. When I do the same thing for a test WinForms application it works fine, however, when I use it in our WinForms system application, the client can't connect to the server. Both client and server are running on my local machine. I am not using any values in their respective application.exe.config files. Before you go and use the code below to try and reproduce, as I said previously this works fine in smaller applications, but not our over a million lines of code application so I'm hoping the seasoned WCF professionals can tell me some things to look for as to why the client is failing. But all I'm doing is using the same exact code located below in between the 3 different applications and it only fails in our system application.
Tests I've run:
System app server running, System app client running, client fails to connect to server.
Console running as server, System app server running (WCF code commented out), System App client running, client connects to console server via WCF and completes method call.
Console running as client, System App server running, client fails to connect to server.
Based on the tests above something seems wrong with the server, but I can't figure out why it only fails in the System app server, but works in the Console and WinForms server test apps using the same client/server code in all of them.
Here's the code from the AppCommWcf library:
Project References:
System
System.Runtime.Serialization
System.ServiceModel
CameraCtrlCommClient.cs:
using System.ServiceModel;
namespace AppCommWcf
{
public partial class CameraCtrlCommClient : ClientBase<ICameraCtrlComm>, ICameraCtrlComm
{
public CameraCtrlCommClient(string bindUrl) : base(new WSHttpBinding(), new EndpointAddress(bindUrl)) { }
public LockRequestResponse RequestLock(LockTypeWcf lckType, string camName, int stationId, string userName, bool takeCtrlIfLocked)
{
return base.Channel.RequestLock(lckType, camName, stationId, userName, takeCtrlIfLocked);
}
}
}
CameraCtrlCommServer.cs:
using System;
using System.ServiceModel;
namespace AppCommWcf
{
[ServiceBehavior(InstanceContextMode = InstanceContextMode.Single)]
public class CameraCtrlCommServer : ICameraCtrlComm
{
public delegate LockRequestResponse LockEventHandler(LockTypeWcf lckType, string camName, int stationId, string userName, bool takeCtrlIfLocked);
public event LockEventHandler LockRequested;
static ServiceHost m_svcHost;
CameraCtrlCommServer() { }
public string BindingUrl { get; private set; }
public void Close()
{
if (m_svcHost != null)
{
m_svcHost.Close();
m_svcHost = null;
}
}
public LockRequestResponse RequestLock(LockTypeWcf lckType, string camName, int stationId, string userName, bool takeCtrlIfLocked)
{
return LockRequested.Invoke(lckType, camName, stationId, userName, takeCtrlIfLocked);
}
public static CameraCtrlCommServer StartServiceHost(string bindUrl)
{
CameraCtrlCommServer result = null;
if (m_svcHost == null)
{
result = new CameraCtrlCommServer { BindingUrl = bindUrl };
m_svcHost = new ServiceHost(result, new Uri(bindUrl))
{
OpenTimeout = new TimeSpan(0, 0, 30),
CloseTimeout = new TimeSpan(0, 0, 10)
};
m_svcHost.AddServiceEndpoint(typeof(ICameraCtrlComm), new WSHttpBinding(), bindUrl);
m_svcHost.Faulted += OnSvcHostFaulted;
m_svcHost.Opened += OnSvcHostOpened;
m_svcHost.Closing += OnSvcHostClosing;
m_svcHost.Closed += OnSvcHostClosed;
m_svcHost.UnknownMessageReceived += OnSvcHostUnknownMsgRecvd;
m_svcHost.Open();
}
else
{
throw new Exception("The service host has already been started.");
}
return result;
}
static void OnSvcHostUnknownMsgRecvd(object sender, UnknownMessageReceivedEventArgs e)
{
}
static void OnSvcHostClosed(object sender, EventArgs e)
{
}
static void OnSvcHostClosing(object sender, EventArgs e)
{
}
static void OnSvcHostOpened(object sender, EventArgs e)
{
}
static void OnSvcHostFaulted(object sender, EventArgs e)
{
}
}
}
Enums.cs:
using System.Runtime.Serialization;
namespace AppCommWcf
{
/// <summary>
/// The type of lock being requested. These values must match BMS.ElectroOptic.C4IAppOffEnum.BMSSensorLockTypeEnum
/// </summary>
[DataContract(Name = "LockTypeWcf", Namespace = "http://schemas.datacontract.org/2004/07/AppCommWcf")]
public enum LockTypeWcf : int
{
/// <summary>
/// The camera is not locked.
/// </summary>
[EnumMember]
NotLocked = 0,
/// <summary>
/// A manual lock was performed by clicking the Lock toolbar button on a video, cannot be overridden except by another
/// user or an <see cref="Entity"/> lock.
/// </summary>
[EnumMember]
Manual,
/// <summary>
/// An action was performed that required an auto-lock and an auto-unlock if not other action is taken within
/// the time specified in C4IAppSimple.BMSSEOMngrParamsOfRoot.AutoUnlockTimeout. Can be overridden by another user
/// or <see cref="Manual"/> or <see cref="Entity"/>.
/// </summary>
[EnumMember]
Auto,
/// <summary>
/// A lock was performed by an entity action, i.e. House Call, Line Scan or Investigation. Can be overridden by
/// another user or <see cref="Manual"/> or <see cref="Auto"/>.
/// </summary>
[EnumMember]
Entity
}
#region LockPromptResponse enum
/// <summary>
/// The possible responses from <see cref="BMSEOMngr.RequestCameraLock(BMSSensorLockTypeEnum, BMSEOChannelEnt, int, string)"/>
/// or <see cref="BMSEOMngr.RequestCameraLock(BMSSensorLockTypeEnum, InfSensorId, int, string)"/>.
/// </summary>
[DataContract(Name = "LockRequestResponse", Namespace = "http://schemas.datacontract.org/2004/07/AppCommWcf")]
public enum LockRequestResponse
{
/// <summary>
/// The video is not locked.
/// </summary>
[EnumMember]
NotLocked = 0,
/// <summary>
/// The video is locked by the current user.
/// </summary>
[EnumMember]
LockedByCurrentUser,
/// <summary>
/// The video is locked by a different user.
/// </summary>
[EnumMember]
LockedByOtherUser
}
#endregion
}
ICameraCtrlComm.cs:
using System.ServiceModel;
namespace AppCommWcf
{
[ServiceContract]
public interface ICameraCtrlComm
{
[OperationContract(Action = "http://tempuri.org/ICameraLockComm/RequestLock", ReplyAction = "http://tempuri.org/ICameraLockComm/RequestLockResponse")]
LockRequestResponse RequestLock(LockTypeWcf lckType, string camName, int stationId, string userName, bool takeCtrlIfLocked);
}
}
Server calling code
using AppCommWcf;
CameraCtrlCommServer m_camCtrlServer;
Main()
{
m_camCtrlServer = CameraCtrlCommServer.StartServiceHost(bindUrl);
m_camCtrlServer.LockRequested += OnServerLockRequested;
}
LockRequestResponse OnServerLockRequested(LockTypeWcf lckType, string camName, int stationId, string userName, bool takeCtrlIfLocked)
{
return LockRequestResponse.LockedByCurrentUser;
}
Client calling code:
using AppCommWcf;
using System.ServiceModel.Channels;
CameraCtrlCommClient m_camCtrlClient;
void Main()
{
TimeSpan timeout = new TimeSpan(0, 0, 15); //So I don't have to wait a minute to find out it failed to connect.
m_camCtrlClient = new CameraCtrlCommClient(bindUrl);
Binding binding = m_camCtrlClient.Endpoint.Binding;
binding.OpenTimeout = timeout;
binding.ReceiveTimeout = timeout;
binding.SendTimeout = timeout;
}
Here's the screenshots of the WCF logs, hopefully it will help, if you want details of a given line let me know and I'll post it in the comments as I don't have enough space here to post the entire log.
Server Log:
Client Log:
The exception text is:
The HTTP request to 'http://localhost:10821/CameraCtrlComm' has exceeded the allotted timeout of 00:00:14.9990000. The time allotted to this operation may have been a portion of a longer timeout.
The problem ended up being I was instantiating the ServiceHost on a non-windows message loop thread. Once I created my own thread and used Application.Run for it to wait in that thread until the application exited, it worked. I hope this helps someone else out.
I created a Windows Forms application that uses a ChromiumBrowser. The application is composed by the follow components:
Main application
Web browser library
Launcher application
When I launch my application normally, the web browser works correctly. If I launch my application from a launcher, the web browser doesn't work. It tolds me the follow error:
Unhandled exception of 'System.IO.FileNotFoundException' in Unknown module.
Cannot load file or assembly 'CefSharp, Version=57.0.0.0, Culture=neutral, PublicKeyToken=40c4b6fc221f4138' or a relative dependency.
Unable to found the specified file.
I need to use the launcher not only for updates, but since the application is distributed on network, sometimes there are problems to access files on the server.
The problem is not relative only to my app. I created a test solution that I posted below and I have had the same issue.
Notes on the project
Cefsharp runtime is located in C:\Program Files (x86)\CEFRuntime\x64 and C:\Program Files (x86)\CEFRuntime\x86. (I created an installer that copy the runtime files in this position). Runtime is based on the NuGet package.
All executables are compiled in AnyCpu (AnyCpu support)
Cefsharp version 57 (Cef redist 3.2987.1601)
Runtime content
x64 folder
cef.pak
CefSharp.BrowserSubprocess.Core.dll
CefSharp.BrowserSubprocess.Core.pdb
CefSharp.BrowserSubprocess.exe
CefSharp.BrowserSubprocess.pdb
CefSharp.Core.dll
CefSharp.Core.pdb
CefSharp.Core.xml
CefSharp.dll
CefSharp.pdb
CefSharp.WinForms.dll
CefSharp.WinForms.pdb
CefSharp.WinForms.XML
CefSharp.XML
cef_100_percent.pak
cef_200_percent.pak
cef_extensions.pak
chrome_elf.dll
d3dcompiler_47.dll
devtools_resources.pak
icudtl.dat
libcef.dll
libEGL.dll
libGLESv2.dll
natives_blob.bin
snapshot_blob.bin
widevinecdmadapter.dll
locales folder (with all .paks)
x86 folder
cef.pak
CefSharp.BrowserSubprocess.Core.dll
CefSharp.BrowserSubprocess.Core.pdb
CefSharp.BrowserSubprocess.exe
CefSharp.BrowserSubprocess.pdb
CefSharp.Core.dll
CefSharp.Core.pdb
CefSharp.Core.xml
CefSharp.dll
CefSharp.pdb
CefSharp.WinForms.dll
CefSharp.WinForms.pdb
CefSharp.WinForms.XML
CefSharp.XML
cef_100_percent.pak
cef_200_percent.pak
cef_extensions.pak
chrome_elf.dll
d3dcompiler_47.dll
devtools_resources.pak
icudtl.dat
libcef.dll
libEGL.dll
libGLESv2.dll
natives_blob.bin
snapshot_blob.bin
widevinecdmadapter.dll
locales folder (with all .paks)
I post the test solution that gives me the same error.
Test Solution
The test solution is composed by three projects:
StackOverflowIssueLauncher
StackOverflowIssue (reference to WebBrowser)
WebBrowser (dll library that contains the webbrowser)
The code is shown below:
Project StackOverflowIssueLauncher
Program.cs
using System;
using System.IO;
using System.Runtime.InteropServices;
using System.Text;
using System.Windows.Forms;
namespace StackOverflowIssueLauncher {
/// <summary>
/// Launcher program
/// </summary>
internal static class Program {
/// <summary>
/// Launcher body
/// </summary>
[STAThread, LoaderOptimization(LoaderOptimization.MultiDomainHost)]
private static void Main() {
//Initialize path of application
string startupPath = Environment.CurrentDirectory;
string cachePath = Path.Combine(Path.GetTempPath(), "Program-" + Guid.NewGuid());
string assemblyPath = CanonicalizePathCombine(startupPath, #"..\..\..\StackOverflowIssue\bin\Debug\");
string executablePath = Path.Combine(assemblyPath, "StackOverflowIssue.exe");
string configFile = executablePath + ".config";
//Start App Domain
try {
var setup = new AppDomainSetup() {
ApplicationName = "StackOverflowIssue",
ShadowCopyFiles = "true",
ShadowCopyDirectories = assemblyPath,
CachePath = cachePath,
ConfigurationFile = configFile
};
var domain = AppDomain.CreateDomain("StackOverflowIssue", AppDomain.CurrentDomain.Evidence, setup);
domain.ExecuteAssembly(executablePath);
AppDomain.Unload(domain);
}
catch (Exception ex) {
MessageBox.Show(ex.Message, "Warning", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
//Empty cache path
try {
Directory.Delete(cachePath, true);
}
catch (Exception) {
//DO NOTHING
}
}
private static string CanonicalizePathCombine(string sourcePath, string destPath) {
string resultPath = Path.Combine(sourcePath, destPath);
var sb = new StringBuilder(Math.Max(260, 2 * resultPath.Length));
PathCanonicalize(sb, resultPath);
return sb.ToString();
}
[DllImport("shlwapi.dll", CharSet = CharSet.Auto, SetLastError = true)]
private static extern bool PathCanonicalize([Out] StringBuilder sb, string src);
}
}
Project StackOverflowIssue
WebControlForm.cs
using System.Windows.Forms;
using WebBrowser;
namespace StackOverflowIssue {
/// <summary>
/// Form that contains the webbrowser control
/// </summary>
public class WebControlForm : Form {
/// <summary>
/// Create a new web control form
/// </summary>
public WebControlForm() {
InitializeComponent();
Controls.Add(new CefControl { Dock = DockStyle.Fill });
}
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.IContainer components = null;
/// <summary>
/// Clean up any resources being used.
/// </summary>
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
protected override void Dispose(bool disposing) {
if (disposing && (components != null)) {
components.Dispose();
}
base.Dispose(disposing);
}
#region Windows Form Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent() {
this.SuspendLayout();
//
// WebControlForm
//
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.ClientSize = new System.Drawing.Size(529, 261);
this.Name = "WebControlForm";
this.Text = "WebControlForm";
this.ResumeLayout(false);
}
#endregion
}
}
MainForm.cs
using System;
using System.Windows.Forms;
namespace StackOverflowIssue {
/// <summary>
/// Main application form
/// </summary>
public partial class MainForm : Form {
/// <summary>
/// Creates the main form
/// </summary>
public MainForm() {
InitializeComponent();
}
/// <summary>
/// Show a new Web Control form
/// </summary>
/// <param name="sender">Object that raised the event</param>
/// <param name="e">Event arguments</param>
private void ShowBtn_Click(object sender, EventArgs e) {
var wcf = new WebControlForm();
wcf.Show(this);
}
/// <summary>
/// Variabile di progettazione necessaria.
/// </summary>
private System.ComponentModel.IContainer components = null;
/// <summary>
/// Pulire le risorse in uso.
/// </summary>
/// <param name="disposing">ha valore true se le risorse gestite devono essere eliminate, false in caso contrario.</param>
protected override void Dispose(bool disposing) {
if (disposing && (components != null)) {
components.Dispose();
}
base.Dispose(disposing);
}
#region Codice generato da Progettazione Windows Form
/// <summary>
/// Metodo necessario per il supporto della finestra di progettazione. Non modificare
/// il contenuto del metodo con l'editor di codice.
/// </summary>
private void InitializeComponent() {
this.ShowBtn = new System.Windows.Forms.Button();
this.SuspendLayout();
//
// ShowBtn
//
this.ShowBtn.Location = new System.Drawing.Point(12, 12);
this.ShowBtn.Name = "ShowBtn";
this.ShowBtn.Size = new System.Drawing.Size(134, 40);
this.ShowBtn.TabIndex = 0;
this.ShowBtn.Text = "Show web browser";
this.ShowBtn.UseVisualStyleBackColor = true;
this.ShowBtn.Click += new System.EventHandler(this.ShowBtn_Click);
//
// MainForm
//
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.ClientSize = new System.Drawing.Size(284, 261);
this.Controls.Add(this.ShowBtn);
this.Name = "MainForm";
this.Text = "Main form";
this.ResumeLayout(false);
}
#endregion
private System.Windows.Forms.Button ShowBtn;
}
}
Program.cs
using System;
using System.Diagnostics;
using System.Windows.Forms;
using WebBrowser;
namespace StackOverflowIssue {
/// <summary>
/// Main application program
/// </summary>
internal static class Program {
/// <summary>
/// Main application program.
/// </summary>
[STAThread] private static void Main() {
WebBrowserInitializer.Initialize();
Debug.Print("Application started");
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new MainForm());
}
}
}
packages.config
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="cef.redist.x64" version="3.2987.1601" targetFramework="net452" />
<package id="cef.redist.x86" version="3.2987.1601" targetFramework="net452" />
<package id="CefSharp.Common" version="57.0.0" targetFramework="net452" />
<package id="CefSharp.WinForms" version="57.0.0" targetFramework="net452" />
</packages>
Project WebBrowser
CefControl.cs
using System.Windows.Forms;
using CefSharp.WinForms;
namespace WebBrowser {
/// <summary>
/// WebBrowser control
/// </summary>
public class CefControl: UserControl {
public CefControl() {
CefInitializer.Initialize();
InitializeComponent();
var cr = new ChromiumWebBrowser("https://www.google.com");
cr.Dock = DockStyle.Fill;
Controls.Add(cr);
}
/// <summary>
/// Variabile di progettazione necessaria.
/// </summary>
private System.ComponentModel.IContainer components = null;
/// <summary>
/// Pulire le risorse in uso.
/// </summary>
/// <param name="disposing">ha valore true se le risorse gestite devono essere eliminate, false in caso contrario.</param>
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}
#region Codice generato da Progettazione componenti
/// <summary>
/// Metodo necessario per il supporto della finestra di progettazione. Non modificare
/// il contenuto del metodo con l'editor di codice.
/// </summary>
private void InitializeComponent()
{
components = new System.ComponentModel.Container();
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
}
#endregion
}
}
CefInitializer.cs
using CefSharp;
using System;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Net;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Windows.Forms;
namespace WebBrowser {
/// <summary>
/// Class that contains the base methods for CEF initializations
/// </summary>
public static class CefInitializer {
/// <summary>
/// Initialize properties
/// </summary>
static CefInitializer() {
CachePath = Path.Combine(Path.GetTempPath(), "SOIssue", "Cache");
LogFile = Path.Combine(Path.GetTempPath(), "SOIssue", "Logs");
UserDataPath = Path.Combine(Path.GetTempPath(), "SOIssue", "Data");
if (!Directory.Exists(CachePath))
Directory.CreateDirectory(CachePath);
if (!Directory.Exists(LogFile))
Directory.CreateDirectory(LogFile);
if (!Directory.Exists(UserDataPath))
Directory.CreateDirectory(UserDataPath);
//Complete the files combine
LogFile = Path.Combine(LogFile, "WebBrowser.log");
AppDomain.CurrentDomain.DomainUnload += (sender, args) => Shutdown();
}
/// <summary>
/// Shutdown all CEF instances
/// </summary>
internal static void Shutdown() {
using (var syncObj = new WindowsFormsSynchronizationContext()) {
syncObj.Send(o => {
if (Cef.IsInitialized)
Cef.Shutdown();
}, new object());
}
}
/// <summary>
/// Initialize CEF libraries
/// </summary>
[MethodImpl(MethodImplOptions.NoInlining)] internal static void Initialize() {
if (Cef.IsInitialized)
return;
//Get proxy properties
WebProxy proxy = WebRequest.DefaultWebProxy as WebProxy;
string cefPath = Path.GetDirectoryName(Assembly.GetAssembly(typeof(Cef)).Location);
Debug.Print($"CEF Library Path: {cefPath}");
Debug.Assert(cefPath != null, nameof(cefPath) + " != null");
var settings = new CefSettings() {
BrowserSubprocessPath = Path.Combine(cefPath, "CefSharp.BrowserSubprocess.exe"),
LocalesDirPath = Path.Combine(cefPath, "locales"),
ResourcesDirPath = cefPath,
Locale = CultureInfo.CurrentCulture.Name,
CachePath = CachePath,
LogFile = LogFile,
UserDataPath = UserDataPath
};
if (proxy == null || proxy.Address.AbsoluteUri != string.Empty)
settings.CefCommandLineArgs.Add("no-proxy-server", string.Empty);
Cef.Initialize(settings);
}
internal static readonly string CachePath;
internal static readonly string LogFile;
internal static readonly string UserDataPath;
}
}
WebBrowserInitializer.cs
using System;
using System.Collections.Generic;
using System.IO;
using System.Reflection;
namespace WebBrowser {
/// <summary>
/// Class that contains the assembly resolve functions
/// </summary>
public static class WebBrowserInitializer {
private static readonly object _initializer = new object();
private static bool _initialized;
/// <summary>
/// Check if the WebBrowser is initialized
/// </summary>
public static bool IsInitialized {
get {
lock (_initializer)
return _initialized;
}
}
/// <summary>
/// Initialize the current assembly
/// </summary>
public static void Initialize() {
lock (_initializer) {
if (!_initialized) {
AppDomain.CurrentDomain.AssemblyResolve += CefSharp_AssemblyResolve;
_initialized = true;
}
}
}
/// <summary>
/// Try to resolve the assembly
/// </summary>
/// <param name="sender">Object that has raised the event</param>
/// <param name="args">Event raised</param>
/// <returns>Assembly loaded</returns>
private static Assembly CefSharp_AssemblyResolve(object sender, ResolveEventArgs args) {
Debug.Print($"Library: {args.Name}");
if (!args.Name.StartsWith("CefSharp", StringComparison.OrdinalIgnoreCase))
return null;
string assemblyName = args.Name.Split(new[] {','}, 2)[0] + ".dll";
foreach (var path in GetAssemblyPaths()) {
string checkPath = Path.Combine(path, assemblyName);
if (File.Exists(checkPath)) {
Debug.Print($"Relative path FOUND for {args.Name} in {checkPath}");
return Assembly.UnsafeLoadFrom(checkPath);
}
Debug.Write($"Relative path not found for {args.Name} in {checkPath}");
}
return null;
}
/// <summary>
/// Get all possible assembly paths
/// </summary>
/// <returns>List of possible assembly paths</returns>
private static IEnumerable<string> GetAssemblyPaths() {
string pathPrefix = Environment.Is64BitProcess ? "x64" : "x86";
if (Directory.Exists(#"C:\Program Files (x86)\CEFRuntime\" + pathPrefix))
yield return #"C:\Program Files (x86)\CEFRuntime\" + pathPrefix;
yield return Path.Combine(AppDomain.CurrentDomain.SetupInformation.ApplicationBase, pathPrefix);
yield return Path.Combine(Environment.CurrentDirectory, pathPrefix);
Assembly currentAssembly = Assembly.GetAssembly(typeof(CefInitializer));
if (!string.IsNullOrEmpty(currentAssembly.Location))
yield return Path.Combine(currentAssembly.Location, pathPrefix);
}
}
}
packages.config
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="cef.redist.x64" version="3.2987.1601" targetFramework="net452" />
<package id="cef.redist.x86" version="3.2987.1601" targetFramework="net452" />
<package id="CefSharp.Common" version="57.0.0" targetFramework="net452" />
<package id="CefSharp.WinForms" version="57.0.0" targetFramework="net452" />
</packages>
Looking at CefSharp General Usage (https://github.com/cefsharp/CefSharp/wiki/General-Usage#need-to-knowlimitation), I noticed a line that explains that CefSharp works only on default AppDomain. I have looked at the project https://github.com/stever/AppHostCefSharp and I found a solution.
I need to run the WebBrowser on a Default AppDomain (I forked and edited RedGate.AppHost repository. See below for why i did it.). To allow communications between controls I implements two NamedPipes services, one on the main form, another on the created object.
I posted the complete solution (https://github.com/rupertsciamenna89/cefsharp-remoting) so the source code will be simplier to see. And it can be improved or fixed (like my english :))
I renamed the original projects to better names.
The solution is composed of 4 projects:
MainApplication [old StackOverflowIssue] (base application that I have to launch with ShadowCopy)
MainApplication.Launcher [old StackOverflowIssue.Launcher] (application launcher)
MainApplication.WebBrowser [old WebBrowser] (winforms controls library that contains the WebBrowser)
MainApplication.Interfaces (interfaces that must be implemented for the operations)
MainApplication.Interfaces
This project contains the interfaces that must be implemented by the client and the server. It contains five files:
IFormService is the interface that allows to create the control throught the RedGate.AppHost. It contains two Guids that identifies the unique names for Control/Server named pipes.
IAppClient is the client interface that will be implemented into the Control library to perform remote calls on the Application.
IAppServer is the server interface that will be implemented into the Application to accept remote calls from the Control library.
IWebBrowserClient is the client interface that will be implemented into Application to perform remote calls on the Control library.
IWebBrowserServer is the server interface that will be implemented into the Control libray to accept remote calls from the Application.
MainApplication.WebBrowser
This project implements the OutOfProcessEntryPoint interface that initializes the Control WCF Service. It contains the implementation of the Server interface and allows the remote client show the folder and retrieve the returned result.
MainApplication
I edited the Program.Main accepting the binaries path. I save this argument into a static variable that I'll use to crate the child process handle. The function that creates the process handle is this:
public static IChildProcessHandle CreateChildProcessHandle() {
string assemblyPath = _sourcePath ?? Path.GetDirectoryName(Assembly.GetAssembly(typeof(WebBrowserInitializer)).Location);
Debug.Assert(assemblyPath != null, "assemblyPath != null");
var al = new ChildProcessFactory() { ClientExecutablePath = _sourcePath };
return al.Create(Path.Combine(assemblyPath, "MainApplication.WebBrowser.dll"), false, Environment.Is64BitProcess);
}
If the source path isn't passed (like if I execute the application directly), RedGate will use the default location (the executing assembly path).
Once the windows is opened, the user could press the Show (or ShowDialog) button. The application "simply" run these lines of code:
//Generates client id and server id
string appId = Guid.NewGuid().ToString("N");
string controlId = Guid.NewGuid().ToString("N");
_service = AppServer.Start(appId, controlId);
_service.FormCompleted += Service_FormCompleted;
_locator = new FormServiceLocator(appId, controlId);
_element = _handle.CreateElement(_locator);
_service.StartRemoteClient();
_service.ShowDialog((long)Handle);
When the user will close the window, the callback function will be called:
private void Service_FormCompleted(object sender, AppServerEventArgs e) {
//Check if invoke is required
if (InvokeRequired) {
Invoke(new Action<object, AppServerEventArgs>(Service_FormCompleted), sender, e);
return;
}
_element = null;
MessageBox.Show(this, $"Result: {e.Result} - Data: {e.AdditionalData}");
}
MainApplication.Launcher
This is the project that launch our application with ShadowCopy enabled. I pass as argument the path of the binaries.
var domain = AppDomain.CreateDomain("CefSharp-Remoting",
AppDomain.CurrentDomain.Evidence, setup);
domain.ExecuteAssembly(executablePath, new[] { $"\"/path:{assemblyPath}\"" });
Why I forked the RedGate.AppHost repository
RedGate.AppHost try to found the Clients application looking into the Assembly location. With ShadowCopy enabled, this is not possible, because the application is copied into a "random" folder, and the Client application is in the source path.
I added the ClientExecutablePath property into the ChildProcessFactory.cs and ProcessStarter.cs, so the ProcessStarter use this folder instead of default folder if this property is setted.
You can see that edits in the follow files:
https://github.com/rupertsciamenna89/RedGate.AppHost/blob/master/RedGate.AppHost.Server/ChildProcessFactory.cs
https://github.com/rupertsciamenna89/RedGate.AppHost/blob/master/RedGate.AppHost.Server/ProcessStarter.cs
I have an application that is used to open files in a document viewer for the purposes of 1: not allowing users to alter the files, and 2: to track the files that are opened and how long they are open for.
That said, I have the files they select (either Word Docs or Excel Workbooks) converted into XPS files and placed in a DocumentViewer in a WPF project.
The first time a document is opened, it works as intended. However, as soon as a second file is attempted to be opened, I get a
System.ObjectDisposedException: Package object was closed and disposed, so cannot carry out operations on this object or any stream opened on a part of this package.
I have been searching for hours now and have no idea what is going on.
Here is the relevant code:
class DocumentViewerFileGenerator
{
/// <summary>
/// Generates a docuemnt, of the IDocumentPaginatorSource type to be used by the document viewer in the
/// view. By looking at the extension type, decides on which interop to use.
/// </summary>
/// <param name="filePath">Path of the file that is to be converted</param>
/// <param name="extension">Extension of the file. Makes it easier for the if's</param>
/// <returns>A converted IDocumentPaginatorSource version of the file to be viewed.</returns>
public XpsDocument GenerateDocumentForViewer(string filePath, string extension)
{
string tempOutputPath = Environment.CurrentDirectory + #"\temp.xps";
ClearOldTemp(tempOutputPath);
XpsDocument xpsDocument;
if (extension == ".doc" || extension == ".docx")
{
ConvertWordToXps(filePath, tempOutputPath);
}
if (extension == ".xls" || extension == ".xlsx")
{
ConvertExcelToXps(filePath, tempOutputPath);
}
xpsDocument = MakeFixedDocument(tempOutputPath);
return xpsDocument;
}
/// <summary>
/// Just clears out the old temp path
/// </summary>
/// <param name="tempOutputPath"></param>
private void ClearOldTemp(string tempOutputPath)
{
if (File.Exists(tempOutputPath))
{
File.Delete(tempOutputPath);
}
}
/// <summary>
/// Converts the file selected, through Word, into an XPS for conversion purposes.
/// </summary>
/// <param name="filePath">The file to be converted. Full path needed.</param>
private void ConvertWordToXps(string filePath, string tempOutputPath)
{
Word.Application word = new Word.Application();
word.DisplayAlerts = Word.WdAlertLevel.wdAlertsNone;
word.Visible = false;
Word.Document document = word.Documents.Open(filePath);
document.SaveAs2(tempOutputPath, FileFormat: Word.WdSaveFormat.wdFormatXPS);
document.Close();
word.Quit();
Marshal.ReleaseComObject(document);
Marshal.ReleaseComObject(word);
}
/// <summary>
/// Converts the file selected, through Excel, into an XPS for conversion purposes.
/// </summary>
/// <param name="filePath">The file to be converted. Full path needed.</param>
private void ConvertExcelToXps(string filename, string tempOutputPath)
{
Excel.Application excel = new Excel.Application();
excel.Visible = false;
excel.DisplayAlerts = false;
Excel.Workbook workbook = excel.Workbooks.Open(filename);
workbook.ExportAsFixedFormat(Excel.XlFixedFormatType.xlTypeXPS, tempOutputPath);
workbook.Close();
excel.Quit();
Marshal.ReleaseComObject(workbook);
Marshal.ReleaseComObject(excel);
excel = null;
}
/// <summary>
///
/// </summary>
/// <param name="tempOutputPath"></param>
/// <returns></returns>
private XpsDocument MakeFixedDocument(string tempOutputPath)
{
return new XpsDocument(tempOutputPath, FileAccess.Read);
}
}
}
This is the ViewModel for displaying the document:
public FileViewerViewModel(string fileName, string exentsion)
{
DocumentViewerFileGenerator generator = new DocumentViewerFileGenerator();
try
{
FileToDisplay = generator.GenerateDocumentForViewer(fileName, exentsion);
FileToShow = FileToDisplay.GetFixedDocumentSequence();
IsMainEnabled.Instance.IsWindowVisible = System.Windows.Visibility.Hidden;
}
catch (Exception log)
{
//Error handeling
}
/// <summary>
/// The file that is to be shown on the view model.
/// </summary>
private IDocumentPaginatorSource fileToShow;
public IDocumentPaginatorSource FileToShow
{
get { return fileToShow; }
set {
if (value == fileToShow)
{ return; }
fileToShow = value;
OnPropertyChanged();
}
}
private XpsDocument FileToDisplay
{
get;
set;
}
/// <summary>
/// Our good, generic PropertyChanged handler. Glorious.
/// </summary>
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
private void ClosingWindow()
{
if (AutoLogoutTimer.Instance.IsLogOff != true)
{
UnlockXPSFile(FileToDisplay);
IsMainEnabled.Instance.IsWindowVisible = System.Windows.Visibility.Visible;
}
}
private void UnlockXPSFile(XpsDocument fileToUnlock)
{
Package xpsPackage = PackageStore.GetPackage(fileToUnlock.Uri);
xpsPackage.Close();
}
}
As I said before, the first time this runs through, it works fine.
However, the second time it goes to make a new xps file at this line:
private XpsDocument MakeFixedDocument(string tempOutputPath)
{
return new XpsDocument(tempOutputPath, FileAccess.Read);
}
The exception gets thrown.
What am I missing here?
Thank you.
Well after doing quite of a bit of head scratching, more Googling and banging my face against the keyboard, it dawned upon me what I was missing...
private void UnlockXPSFile(XpsDocument fileToUnlock)
{
Package xpsPackage = PackageStore.GetPackage(fileToUnlock.Uri);
xpsPackage.Close();
PackageStore.RemovePackage(fileToUnlock.Uri); //This line right here
}
While I was closing the package, I wasn't actually removing it from the package store. So, the application was looking for the same package store to store the new XPS folder in, but since it was closed and dumped, it had no where to go.
I'm in face on the following problem: An application developed on Microsoft Visual Studio 2013 in .NET 4.5, needs to work in Window XP Platforms. I'm rebuild the software using .NET 4.0 and make some modifications to add compatibility, but when i click in a button the app crash and don't show a clear error message and the Trace resource don't log anything. On application start i had a little window that ask user to put your name, and this feature works fine. Anybody have any suggestion of what can i do ?
EDIT 1:
The follow code is the root of problems, this code was compiled using .NET 4.0:
SerialManager.cs
using System;
using System.Windows;
using TestSat;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.IO.Ports;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading.Tasks;
using System.Threading;
using System.Text.RegularExpressions;
using System.Diagnostics;
namespace TestSat.DataModel
{
/// <summary>
///
/// </summary>
public class SerialManager : INotifyPropertyChanged
{
#region Events
public event PropertyChangedEventHandler PropertyChanged;
/// <summary>
///
/// </summary>
/// <param name="propertyName"></param>
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
/* [AttributeUsage(AttributeTargets.Parameter, AllowMultiple = false, Inherited = true)]
public sealed class CallerMemberNameAttribute : Attribute
{
}*/
/// <summary>
///
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="field"></param>
/// <param name="value"></param>
/// <param name="propertyName"></param>
/// <returns></returns>
protected bool SetField<T>(ref T field, T value, [CallerMemberName] string propertyName = null)
{
if (EqualityComparer<T>.Default.Equals(field, value)) return false;
field = value;
OnPropertyChanged(propertyName);
return true;
}
#endregion
#region Private Fields
private static SerialPort PortaSerial;
private ObservableCollection<String> mPorts;
private String mSelectedPort;
private int mBaudRate = 115200;
private int mDataBits = 8;
#endregion
#region Public Fields
public StringBuilder logText;
#endregion
#region Properties
/// <summary>
///
/// </summary>
public ObservableCollection<String> COMPorts
{
get { return mPorts; }
set { SetField(ref mPorts, value); }
}
/// <summary>
///
/// </summary>
public String TextoLog
{
get { return logText.ToString(); }
set
{
if (logText.Length >= logText.MaxCapacity)
{
logText.Clear();;
logText.Append(value);
}
else
{
logText.Append(value);
//MainWindow.last = value;
}
OnPropertyChanged("TextoLog");
}
}
/// <summary>
///
/// </summary>
public String SelectedPort
{
get { return mSelectedPort; }
set {SetField(ref mSelectedPort, value); }
}
#endregion
#region Construtors
/// <summary>
///
/// </summary>
public SerialManager()
{
InitComponents();
}
/// <summary>
///
/// </summary>
private void InitComponents()
{
RefreshPorts();
/*Initialize the log variable*/
logText = new StringBuilder();
/* Update selected port */
SelectedPort = COMPorts.Count > 0 ? COMPorts[0] : "";
}
#endregion
#region Public Methods
/// <summary>
///
/// </summary>
public void RefreshPorts()
{
// Update ports
string[] pPorts = SerialPort.GetPortNames();
// Sort alphabetically
Array.Sort(pPorts);
// Sort by string length
Array.Sort(pPorts, (x, y) => x.Length.CompareTo(y.Length));
// Create collection
COMPorts = new ObservableCollection<string>(pPorts);
}
/// <summary>
///
/// </summary>
/// <param name="mSelectedPort"></param>
public void ConnectSerial(String mSelectedPort)
{
PortaSerial = new SerialPort();
PortaSerial.PortName = mSelectedPort;
PortaSerial.BaudRate = mBaudRate;
PortaSerial.Parity = Parity.None;
PortaSerial.DataBits = mDataBits;
PortaSerial.StopBits = StopBits.One;
PortaSerial.Handshake = Handshake.None;
PortaSerial.DataReceived += new SerialDataReceivedEventHandler(DataReceivedHandler);
Trace.WriteLine("DataReceived definida");
try
{
PortaSerial.Open();
}
catch (SystemException)
{
MessageBox.Show("A porta serial esta sendo usada em outra aplicação.", "Erro", MessageBoxButton.OK);
throw new SystemException();
}
}
/// <summary>
///
/// </summary>
public void DesconnectSerial()
{
if (PortaSerial.IsOpen)
{
PortaSerial.Close();
}
}
/// <summary>
///
/// </summary>
public void writeSerial(String text)
{
if (PortaSerial.IsOpen)
{
if (text.Length > 0)
{
/* char[] array = text.ToCharArray(0,text.Length);
foreach(char ch in array)
{
PortaSerial.Write(ch.ToString());
Thread.Sleep(50);
}*/
PortaSerial.WriteLine(text);
}
else
{
PortaSerial.WriteLine("");
}
}
else
{
MessageBox.Show("Porta serial não esta aberta.", "Erro", MessageBoxButton.OK);
Console.WriteLine("Porta serial não esta aberta");
}
}
/// <summary>
///
/// </summary>
public bool IsOpen()
{
return PortaSerial.IsOpen;
}
/// <summary>
///
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void DataReceivedHandler(object sender, SerialDataReceivedEventArgs e)
{
MainWindow.StartRawData = true;
SerialPort sp = (SerialPort)sender;
string indata = sp.ReadExisting();
TextoLog = indata;
/*Omited code only logical operation*/
}
#endregion
}
}
If i don't do any instance or reference to serial port the applications don't crashes. Exist a way to force this part of code compiled by .NET 3.5? Or exist another suggestion of solution for this problem?
The solution i found at http://blogs.msdn.com/b/bclteam/p/asynctargetingpackkb.asp , Issue 8. Installing the .NET 4.0 update the problems don't occured anymore.
I came up with this example to clarify my question
We start with a base class
/// <summary>
/// Just a silly example class
/// </summary>
class CFileStream
{
protected readonly string FilePath;
public CFileStream(string filePath)
{
FilePath = filePath;
}
public virtual void Write(string s)
{
var stream = GetStream(FilePath);
//etc
}
/// <summary>
/// Take filePath as an argument to make subclassing easier
/// </summary>
protected virtual FileStream GetStream(string filePath)
{
return new FileStream(filePath, FileMode.OpenOrCreate);
}
}
Create a subclass for it
/// <summary>
/// Building on top of CFileStream, created an encrypted version
/// </summary>
class CFileStreamEncrypted : CFileStream
{
private readonly string _key;
public CFileStreamEncrypted(string filePath, string key):base(filePath)
{
_key = key;
}
/// <summary>
/// For added complexity, let's also wrap a possible excepton
/// </summary>
public override void Write(string s)
{
try
{
base.Write(s);
}
catch (ImaginaryCryptoException ex)
{
throw new ImaginaryCustomException("bladibla", ex);
}
}
/// <summary>
/// Wrap the base stream in an imaginary crypto class
/// </summary>
protected override FileStream GetStream(string filePath)
{
return new CImaginaryCryptoStream(base.GetStream(filePath), _key);
}
}
Now we wish to create a second subclass, but one that works with the initial filewriter as well as the encrypted version.
The first one makes sense
/// <summary>
/// Building on top of CFileStream, created an auto-split version
/// </summary>
class CFileStreamSplit : CFileStream
{
public CFileStreamSplit(string filePath)
: base(filePath)
{
}
protected int Counter;
/// <summary>
/// Close stream and move to next file at the appropriate time(s)
/// </summary>
public override void Write(string s)
{
do
{
Stream stream;
if (ImaginaryBooleanMustSplit)
stream = GetStream(FilePath);
//etc
} while (ImaginaryBooleanDataLeftToWrite);
}
/// <summary>
/// Get base stream but with altered filePath
/// </summary>
protected override FileStream GetStream(string filePath)
{
return base.GetStream(GetNextpath(filePath));
}
/// <summary>
/// Ignore proper extension / file-exists etc.
/// </summary>
protected virtual string GetNextpath(string filePath)
{
return filePath + ++Counter;
}
}
The second one (below this) is completely duplicate code, except for the constructor which now also requires the encryption key.
/// <summary>
/// Build the same auto-split version but this time on top of the encrypted subclass
/// </summary>
class CFileStreamSplitEncrypted : CFileStreamEncrypted
{
public CFileStreamSplitEncrypted(string filePath, string key)
: base(filePath, key)
{
}
/*
* Note that there are no changes below this line
*/
protected int Counter;
/// <summary>
/// Close stream and move to next file at the appropriate time(s)
/// </summary>
public override void Write(string s)
{
do
{
Stream stream;
if (ImaginaryBooleanMustSplit)
stream = GetStream(FilePath);
//etc
} while (ImaginaryBooleanDataLeftToWrite);
}
/// <summary>
/// Get base stream but with altered filePath
/// </summary>
protected override FileStream GetStream(string filePath)
{
return base.GetStream(GetNextpath(filePath));
}
/// <summary>
/// Ignore proper extension / file-exists etc.
/// </summary>
protected virtual string GetNextpath(string filePath)
{
return filePath + ++Counter;
}
}
There are of course a lot of ways to reduce the amount of duplicate code here, but I have yet to find 'the best' way, if there even is such a thing. So; what is the least time-consuming, cleanest, most flexible way to get a round this issue in your opinion/experience?
For the different modifications a decent way to go may be composition over inheritance. Set up your classes to only be responsible for a single thing, taking in a base stream on constructions.
interface ICFileStream
{
void Write(string s);
FileStream GetStream(string filePath);
}
/// <summary>
/// Just a silly example class
/// </summary>
class CFileStream: ICFileStream
{
protected readonly string FilePath;
public CFileStream(string filePath)
{
FilePath = filePath;
}
public void Write(string s)
{
var stream = GetStream(FilePath);
//etc
}
/// <summary>
/// Take filePath as an argument to make subclassing easier
/// </summary>
protected FileStream GetStream(string filePath)
{
return new FileStream(filePath, FileMode.OpenOrCreate);
}
}
/// <summary>
/// Building on top of CFileStream, created an encrypted version
/// </summary>
class CFileStreamEncrypted : ICFileStream
{
private readonly string _key;
private readonly ICFileStream _stream;
public CFileStreamEncrypted(string key, ICFileStream stream)
{
_key = key;
_stream = stream;
}
/// <summary>
/// For added complexity, let's also wrap a possible excepton
/// </summary>
public void Write(string s)
{
try
{
_stream.Write(s);
}
catch (ImaginaryCryptoException ex)
{
throw new ImaginaryCustomException("bladibla", ex);
}
}
/// <summary>
/// Wrap the base stream in an imaginary crypto class
/// </summary>
protected FileStream GetStream(string filePath)
{
return new CImaginaryCryptoStream(_stream.GetStream(filePath), _key);
}
}
class CFileStreamSplit : ICFileStream
{
private readonly ICFileStream _stream;
public CFileStreamSplit(ICFileStream stream)
{
_stream = stream;
}
protected int Counter;
/// <summary>
/// Close stream and move to next file at the appropriate time(s)
/// </summary>
public void Write(string s)
{
do
{
Stream stream;
if (ImaginaryBooleanMustSplit)
stream = GetStream(FilePath);
//etc
} while (ImaginaryBooleanDataLeftToWrite);
}
/// <summary>
/// Get base stream but with altered filePath
/// </summary>
protected FileStream GetStream(string filePath)
{
return _stream.GetStream(GetNextpath(filePath));
}
/// <summary>
/// Ignore proper extension / file-exists etc.
/// </summary>
protected string GetNextpath(string filePath)
{
return filePath + ++Counter;
}
}
So when you want a splitting-crypto-filestream:
new CFileStreamSplit(new CFileStreamEncrypted("crypto-awesome-key", new CFileStream("C:\\blah...")));
This is more flexible so when you want to add LoggingCFileStream for example you don't need to add a separate class for each combination.