A design for adding resources to a project - c#

I have classes Project,Resource and File.
where
A Project contains LIST of Resources.
Each Resource contains LIST of of Files of particular type.
This is mapped to an XML :
<Project>
<Resource id=1>
<File id="1" path="" type="A" />
<File id="2" path="" type="B" />
<File id="3" path="" type="B" />
<File id="4" path="" type="B" />
</Resource>
<Resource id=2>
<File id="1" path="" type="A" />
<File id="2" path="" type="B" />
<File id="3" path="" type="B" />
<File id="4" path="" type="B" />
</Resource>
</Project>
So basically every resource has to have at-most one file of type "A" and any number of files of type "B" . The file type is selected by user from a dialog where he selects the file and adds to the resource.
The problem is for every file of type "A", i need to create a new Resource and hence and new node in XML.(which my current code isn't able to do)
Initially i came with the following (generalised for brevity)
Project p =new Project("Untitled project"); //Will happen once per project
Resource res = p.CreateProjectResource("resource1");
//various params to create resource
p.AddResource(res);
//now lets add files to a resource
AddFileHelper(res,"C:\myfile1.bin","A",guid.toString());
AddFileHelper(res,"C:\myfile32.bin","B",guid.toString());
AddFileHelper(res,"C:\myfile56.bin","B",guid.toString());
//The next statement should create a new resource and add the file to
//the new created design
AddFileHelper(res,"C:\myfile4.bin","A",guid.toString()); //
//some helper class :
//Adds a file of type "type" to a resource "res" with file ID as "id"
private AddFileHelper(Resource res,string path,FileType type,string id)
{
// path is user defined file path from OpenFile dialog,
//type is selected from a Dropdown (of Enum values "A","B",...)
//id is GUID
res.AddFile(path,type,id);
//************ OR it could be also written as *******
//ResFile file =new ResFile(path,type,id);
//res.AddFile(file);
//Update XML file here..
}
The main problem is the User does not create the resources "explicitly" (except for the first resource) and creation of new Resource depends on the type of the file being added by the user.
Also due this design it is difficult to figure out the Resource given a File id.
Only way to track is using the file collection in each Resource class.
Any help??
Thanks All.
This is in reference to a question I asked before post

The problem as I understand it:
As of now, your AddFileHelper only adds files to your project resource labeled ''resource1'' which is a problem because every time a filetype “A” is passed your AddFileHelper, you to make a new resource for your project (''resource2'') and add it to that.
There is a very simple way to do this. Within AddFileHelper test the FileType of the added file and determine whether or not you need a new resource to be added to your project. If the type isn't “A” you'll call the code that you have now and add the file with:
res.AddFile(path, type, id);
If the type to add is “A” and you need a new resource, just redefine res and increment a counter variable of how many resources you have in your project:
Resource res = p.CreateProjectResource(resourceName);
resourceCounter++;
Where resourceName is the string:
string resourceName = ''resource'' + resourceCounter;
All this should be implemented as your AddFileHelper method.
Regarding the overall structure of your code, the AddFileHelper should be a project class method. Here's why:
The AddFile method, and the AddFileHelper method sound similar but do two very different things. The AddFile method belongs to the resource class because it acts on a well defined resource object. However, the AddFile method is not enough for your purposes because the resource file to append to is not immediately apparent to the client who has a file and wants to add it to your project. Bbefore the AddFile method can be called, the target resource needs to be determined. The job of the AddFileHelper method is to determine which resource will call the AddFile method. Therefore, the AddFileHelper method belongs to the project class and the AddFile method to the resource class.
The logical connection between the AddFileHelper method and the project class might be more apparent if you renamed the method to FileResourceAssignment or something like that.

Related

Write Properties (name=value) to a file from an MSBuild project

For an MSBuild project, I would like to output some kind of a .config file that would be redistributed along the generated binary so the parameters used at build time can be checked by the users of the binary, programmatically.
Output file format:
PropertyName1=ValueA
PropertyName2=ValueB
...
Ideally, the list of properties to write would contain just their names. Maybe like:
<ItemGroup>
<MyExposedDictionary Include="Configuration" />
<MyExposedDictionary Include="Platform" />
<MyExposedDictionary Include="PropertyName1" />
...
</ItemGroup>
With MyExposedDictionary being the argument to give to some DotConfigFileWriter task, as well as the path of the destination file.
I found several ways to write down values to a file, including a sub-target with some C# code in it, but I'm new to MSBuild and I'm not sure how I can merge those requirements into a single Target to make it re-usable.
In case someone comes here with the same requirement, here is what I ended up with. Not really happy with the result as I was hoping for something more generic but at least it does the job and blends well in my project:
<Target Name="WriteBuildProperties" BeforeTargets="PreBuildEvent">
<WriteLinesToFile File="$(DotConfigFile)" Overwrite="true" Lines="" />
<WriteLinesToFile File="$(DotConfigFile)" Lines="ProjectName=$(ProjectName)" />
<WriteLinesToFile File="$(DotConfigFile)" Lines="Configuration=$(Configuration)" />
...
</Target>
If someone happen to have a more elegant solution, please jump in!
I am not sure where your problem is located. I have a similar requirement that a file is created by the program which just was compiled. I edited the properties of the project: in the build events enter a Post-build action like
REM create special file
"$(ProjectDir)$(OutDir)MyProgram.exe" /WriteFile MyFile.xml
Of course, you must also change your program such that it does the right thing when called with that parameter (and stops after having completed that action - does not show a GUI or start as a Windows Service).

WIX cannot write value to registry if key contains space

I need to enable IE feature for WebBrowser control. To emulate IE11, I need to write a value to registry key
HKEY_CURRENT_USER\Software\Microsoft\Internet Explorer\Main\FeatureControl\FEATURE_BROWSER_EMULATION
This manipulation need to be done during installation.
Here is my code in WIX script:
<?define var.IEFeatureEmulationKey = "Software\Microsoft\Internet Explorer\FeatureControl\FEATURE_BROWSER_EMULATION" ?>
....
<Fragment>
<DirectoryRef Id="TARGETDIR">
<Component Id="registryValues" Guid="{some-guid}" >
<RegistryKey Root="HKCU" Key="$(var.IEFeatureEmulationKey)" Action="create">
<RegistryValue Name="MyApp.EXE" Value="11000" Type="integer" Action="write"/>
</RegistryKey>
</Component>
</Fragment>
This code work only if $(var.IEFeatureEmulationKey) contains no spaces. But I need to write a value to this specific key.
Please help, how tell WiX to write value to registry even in registry key contains spaces.
UPD:
Added appropriate issue in WiX repository
This Blogpost uses some special syntax I have not seen before either. It states that variable can be defined this way, even if it contains a white space character like so:
<!–?define var.IEFeatureEmulationKey = "Software\Microsoft\Internet Explorer\FeatureControl\FEATURE_BROWSER_EMULATION" ?>
The !- let aside, it looks quite similar to what you got. However, your value contains more than one whitespaces. I'm not sure if thats the value you truly want or if it was just for pointing the whitespace out clearly...
Still cannot overcome the issue, however I want to show a workaround that helped me.
I used custom action that allowed in WiX. First of all I added custom .NET assembly with following method within it
[CustomAction]
public static ActionResult SetRegistryItems(Session session)
{
session.Log("Begin SetRegistryItems");
try
{
// this private method actually does manipulation with registry
SetRegistry();
}
catch (Exception e)
{
session.Log(e.ToString());
}
return ActionResult.Success;
}
Then this method should be referenced in WiX config file (.wxs)
<Fragment>
...
<CustomAction Id='SetRegistryItems' BinaryKey='<NameOfTheAssemblyWithoutExtension>' DllEntry='SetRegistryItems' Execute='immediate'/>
...
<InstallExecuteSequence>
...
<Custom Action="SetRegistryItems" Before="LaunchConditions"/>
...
</InstallExecuteSequence>
...
</Fragment>

WiX – copy arbitrary files

The folder where my setup.exe is located contains a subfolder CALhaving files named something like xyz1234.cal – their names vary from customer to customer. These files have to be copied into a folder CAL in the target directory.
So I created a CustomAction and a C# dll which uses the File.Copy() function. My C# function receives the strings srcDir and destDir as parameters, e.g. D:\installation\CAL and C:\MyApp\CAL.
However, when I check the existence of the folders with Directory.Exists(srcDir), an Exception is thrown, although the directory D:\installation\CAL exists:
ERROR in custom action myFunction System.IO.DirectoryNotFoundException: Could not find a part of the path 'C:\Windows\Installer\MSID839.tmp-\D:\installation\CAL'.
This happens no matter whether the CustomAcion is executed immediate or deferred. C:\Windows\Installer\MSID839.tmp-\ seems to be the path of the executed assembly, but I certainly don’t want to have it as a part of the FullPath. How can I get rid of it?
CustomAction and properties are defined like so:
<CustomAction Id='myCA' BinaryKey='myCABin' DllEntry='myFunction' Execute="deferred" HideTarget="no" Impersonate="no"/>
<Property Id="myCA" Value="Arg1=[CURRENTDIRECTORY];Arg2=[INSTALLDIR]" />
The parameters are used like so:
CustomActionData data = session.CustomActionData;
string srcDir = data["Arg1"]+ "\\CAL";
string destDir = data["Arg2"]+ "\\CAL";
if (Directory.Exists(srcDir))
// copy files
I recreated your app and it works fine. Here is my wix code (it's inside my product node):
<CustomAction Id='Test' BinaryKey='RegistryHelperCA' DllEntry='Test' Execute="deferred" HideTarget="no" Impersonate="no"/>
<Property Id="Test" Value="Arg1=[CURRENTDIRECTORY];Arg2=[INSTALLDIR]" />
<InstallExecuteSequence>
<Custom Action="Test" After="InstallFiles"></Custom>
</InstallExecuteSequence>
My custom action:
[CustomAction("Test")]
public static ActionResult Test(Session session)
{
string dir = session.CustomActionData["Arg1"];
session.Log("DIRECTORY equals " + dir);
if (Directory.Exists(dir))
session.Log("Success");
return ActionResult.Success;
}
It spits out dir as C:\Users\user\Desktop. Verify you aren't assigning to your CURRENTDIRECTORY property anywhere and, if you don't find anything, try setting your custom action to Execute="immediate" and accessing the data like this
string srcDir = session["CURRENTDIRECTORY"]+ "\\CAL";
If that doesn't work, surely that property is being overwritten somewhere. Good luck!
After some trial-and-error-sessions I found that Directory.Exists(srcDir) and Directory.Exists(destDir) didn't work, because the not the values but the property names are passed as parameter to the Exist() function - in contrast to session.Log(srcDir) which properly yields the value.
Finally I ended up with setting execute="immediate" and retrieving the values like so:
srcDir = session["CURRENTDIRECTORY"];
destDir = session.GetTargetPath("INSTALLDIR");

DNN multiple modules on a single package

Is it possible to create a dotnetnuke (v5) package that contains multiple modules?
I mean: there is a "moduleDefinitions" element on the manifest that looks like supporting more than one module, but adding another child to it doesn't sort any effect.
The dnn manifest looks like:
<dotnetnuke type="Package" version="5.0">
<packages>
<package name="MyModuleName" type="Module" version="01.00.00">
<friendlyName>MyModuleName</friendlyName>
<components>
<component type="Module">
<desktopModule>
<moduleName>MyModuleName</moduleName>
<foldername>MyModuleName</foldername>
<supportedFeatures />
<businessControllerClass />
<moduleDefinitions>
<moduleDefinition>...</moduleDefinition>
<!-- this one is ignored -->
<moduleDefinition>...</moduleDefinition>
<moduleDefinitions>
Anyway I need to package two or more modules on the same package. Is it possible?
Multiple module definition elements should work just fine. If there are multiple definitions within a module, when you add the module to the page, an instance of each definition will be added. There will still only be one entry in the extensions list.
If you want two independent modules, you will need two <package> elements (it won't work to have two <component type="Module"> elements in a <package>).

Best way to dynamically set an appender file path

I am trying to find somebody smarter than me to validate some syntax I wrote up. The idea is to configure the filename of my RollingFileAppender to the name of the assembly in order to make it more re-usable for my projects.
I've seen this previous SO article but it wasn't exactly able to answer my question...
I've had a dickens of a time trying to understand the inner components of Log4net and this is what I came up with (residing in the Global.asax file - Application_Start method):
// Bind to the root hierarchy of log4net
log4net.Repository.Hierarchy.Hierarchy root =
log4net.LogManager.GetRepository()
as log4net.Repository.Hierarchy.Hierarchy;
if (root != null)
{
// Bind to the RollingFileAppender
log4net.Appender.RollingFileAppender rfa =
(log4net.Appender.RollingFileAppender)root.Root.GetAppender("RollingLogFileAppender");
if (rfa != null)
{
// Set the file name based on the assembly name
string filePath =
string.Format("~/App_Data/{0}.log", GetType().Assembly.GetName().Name);
// Assign the value to the appender
rfa.File = Server.MapPath(filePath);
// Apply changes to the appender
rfa.ActivateOptions();
}
}
Can anyone tell me, 'this is hideous', or 'this should work fine'? Also, if I set the file dynamically can I still expect the log4net behavior to rotate the files based on the log4net.config file settings?
Much appreciated!
You are doing this the hard way! Define your log4net config as XML in your application's configuration file and use %property{} to advantage:
<appender name="YourAppender" type="log4net.Appender.RollingFileAppender">
<file type="log4net.Util.PatternString" value="~/App_Data/%property{LogName}" />
....
</appender>
This is dynamic -- you just have to set the log4net property "LogName" before you initialize log4net. Thus, in your code any time before you configure log4net, set the desired value of this property:
string LogName = GetType().Assembly.GetName().Name + ".log";
log4net.GlobalContext.Properties["LogName"] = LogName;
Of course, you may use any property name. I've chosen "LogName" for a simple example, but you can have one per application if you want, as long as your code knows what the correct property name is and what the correct value should be.
Here is way to set or change the logfile of the first appender at runtime:
var appender = (log4net.Appender.FileAppender)LogManager.GetRepository().GetAppenders()[0];
appender.File = "C:\whatever.log";
appender.ActivateOptions();
In 2015, we do it like this:
<file type="log4net.Util.PatternString">
<conversionPattern value="%appdomain.log" />
</file>
No other code required.
App domain is the executing assembly's file name.
it worked for me with the date
<file type="log4net.Util.PatternString" value="./Log/logQueueService%date{yyyy_MM_dd}.log" />

Categories