Cross-Platform reading of xml files - c#

I have some Config files as part of my solution on Windows Mobile. I am porting the code to MonoForAndroid and MonoTouch, so I want to keep my code unchanged as much as possible.
When loading these xml files, on Windows Mobile works fine, in my last prototype it also worked on iOS, but the code does not work on MonForAndroid
I have these files
/solution folder
/My Documents/
/Business
App.Config
Settings.Config
I have these files build action set to Content and I can see that they are being copied to the /bin/Debug/ but When I try to read these files, I get the following exception:
System.IO.DirectoryNotFoundException
I see that there is a similar question in here, but they advised to use AndroidResources, which I do not want to do, there are many placed where these files are needed, so I do not want to change it in many places.
AndrodiResources, is out of the question, and if possible I would like to avoid using EmbededResources
ah and the way I am reading it, very straightforward xmDoc.Load(filePath) I also tried File.ReadAllText() I made sure that the filePath is correct, and I got the path generated using Path.Combine() to avoid any issues with the filePath/slashes
Here is how I construct my file path
var filePath = Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().GetName().CodeBase).Replace(FileURIPrefix, ""), "My Documents", "Business");
filePath = Path.Combine(filePath, "App.Config");
And I can see in the debugger that the filePath is correct
Thanks for the help in advance

After searching all around, I could not get the MonoDroid to load (or include) my files when their build action is set to Content.
I had to create an entity called FileHelper which is implemented differently on Android, I then use that FileHelper.ReadAllText(string filename);
I will put my implementation here, hoping that it would benefit somebody else.
Windows Mobile and iOS
public class FileHelper
{
public static string ReadAllText(string filePath)
{
var path = filePath.GetFullPath();
if (!File.Exists(path))
{
Logging.LogHandler.LogError("File " + path + " does not exists");
return string.Empty;
}
using (var reader = new StreamReader(filePath))
{
return reader.ReadToEnd();
}
}
}
Android version
public class FileHelper : BaseFileHelper
{
public static string ReadAllText(string filePath)
{
var entryAssemblyPath = Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().GetName().CodeBase).Replace("file:", ""), "MyExecutableAssemblyName.dll");
// This is because Assembly.GetEntryAssembly() returns null on Android... Booohhh
var assembly = Assembly.LoadFrom(entryAssemblyPath);
using (var stream = assembly.GetManifestResourceStream(filePath.GetFullPath()))
{
using (var reader = new StreamReader(stream))
{
return reader.ReadToEnd();
}
}
}
}
I had a shared code for Constants and an extention method for paths as below
Constants.cs
public static Class Constants
{
private static string _RootPath;
private static string _iOSRootPath;
private static string _AndroidResourcePath;
public static string RootPath
{
get
{
if (string.IsNullOrEmpty(_RootPath))
{
_RootPath = Path.GetDirectoryName(Assembly.GetExecutingAssembly().GetName().CodeBase).Replace(FileURIPrefix, "") + "\\My Documents\\Business";
}
return _RootPath;
}
}
public static string iOSRootPath
{
get
{
if (!string.IsNullOrEmpty(_iOSRootPath))
{
_iOSRootPath = Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().GetName().CodeBase).Replace(FileURIPrefix, "").Replace("file:", ""), Path.Combine("My_Documents", "Business"));
}
return _iOSRootPath;
}
}
public static string AndroidResourcePath
{
get
{
if (string.IsNullOrEmpty(_AndroidResourcePath))
{
_AndroidResourcePath = "Leopard.Delivery.My_Documents.Business.";
}
return _AndroidResourcePath;
}
}
}
PathExtentions.cs
public static class PathExtensions
{
public static string GetFullPath(this string filePath)
{
if (Platform.IsAndroid) // platform is a class that I have to tell me which platfrom I am at :)
{
return Constants.AndroidResourcePath + filePath;
}
if (Platform.IsIOS)
{
return Path.Combine(Constants.iOSRootPath, filePath);
}
return Path.Combine(Constants.RootPath, filePath);
}
}
After setting this up, I am using my FileHelper just as easy as below
string configuratinContents = FileHelper.ReadAllText(configruationPath);
To whoever using this code, remember to set the build action to EmbededResources on Android, and to Content on iOS and Windows Mobile.

Related

.Net MAUI IFilePicker error, Value does not fall within the expected range, if built with msbuilder

I'm building an app for windows only, that needs to consume a docx file using .net MAUI.
I use the suggested class IFilePicker, implemented it, and worked fine while debugging (both in debug and release mode).
So, after finished a preview version, I want to deploy unpacked, "like a portable version", using:
MSBuild.exe D:\Workspace\dotNet\WordReplacer\WordReplacer.App\ /restore /t:Publish /p:TargetFramework=net6.0-windows10.0.19041 /p:configuration=release /p:WindowsAppSDKSelfContained=true /p:Platform=x64 /p:PublishSingleFile=true /p:WindowsPackageType=None /p:RuntimeIdentifier=win10-x64
Everthing works fine just as debug, except for the FilePicker that doesn't work and gives me the error:
Value does not fall within the expected range
This error doesn't happen if I install a published package with a certificate. So maybe I'm missing something in the msbuilder workaround to generate an unpacked app.
I'm using the communitytoolkit.MVVM and the method that I use to pick the file stays in my viewmodel:
private string _inputFilePath;
[ObservableProperty]
private string _inputFileNameText = "Select a input file";
[RelayCommand]
public async Task PickInputDocAsync()
{
try
{
var customFileType = new FilePickerFileType(
new Dictionary<DevicePlatform, IEnumerable<string>>
{
{ DevicePlatform.WinUI, new[] { ".doc", ".docx" } },
});
PickOptions options = new()
{
PickerTitle = "Please select a document",
FileTypes = customFileType,
};
var result = await FilePicker.Default.PickAsync(options).ConfigureAwait(false);
if (result != null)
{
_inputFilePath = result.FullPath;
InputFileNameText = result.FileName;
}
}
catch (Exception ex)
{
ErrorMessage = $"{ex.InnerException?.Message} Error: {ex.Message}, InputFilePath: {_inputFilePath}, InputFileName: {InputFileNameText}";
}
Any clue how to fix it?
I've been trying to fix this error, but since I don't know much about msbuild itself, I take another approach.
So, I finally got it working when I implemented a FilePicker specifically for the Windows platform.
I take most of the code from this answer Folder Picker .NET MAUI, but, instead of folderpicker I use the filepicker. Check it out to know more about the implementation and the set up.
How I do it:
Create a Folder helper in the root folder app and create an interface ICustomPicker and a data transfer object(DTO).
public interface ICustomPicker
{
Task<FileDto> PickFileAsync();
}
public class FileDto
{
public string DisplayName { get; set; }
public string Name { get; set; }
public string FullPath { get; set; }
}
Inside the Plataforms/Windows folder I created CustomFilePicker.cs
public class CustomPicker : ICustomPicker
{
public async Task<FileDto> PickFileAsync()
{
try
{
var picker = new WindowsFilePicker();
picker.FileTypeFilter.Add(".docx");
picker.FileTypeFilter.Add(".doc");
// Get the current window's HWND by passing in the Window object
var windowHandle = ((MauiWinUIWindow)App.Current.Windows[0].Handler.PlatformView).WindowHandle;
// Associate the HWND - window handler, with the file picker
WinRT.Interop.InitializeWithWindow.Initialize(picker, windowHandle);
var storageFile = await picker.PickSingleFileAsync();
if (storageFile != null)
{
return new FileDto()
{
DisplayName = storageFile.DisplayName,
Name = storageFile.Name,
FullPath = storageFile.Path
};
}
}
catch
{
// Ignored
}
return null;
}
}
Register the DI in my MauiProgram.cs
#if WINDOWS
builder.Services.AddTransient<ICustomPicker, Platforms.Windows.CustomPicker>();
#endif
then in my view model I simply call it
private readonly ICustomPicker _customPicker;
public MainViewModel(ICustomPicker customPicker,)
{
_customPicker = customPicker;
}
[RelayCommand]
public async Task PickInputDocAsync(){
var file = await _customPicker.PickFileAsync().ConfigureAwait(false);
if (file != null)
{
//do something
}
}
Probably this problem will disappear (I hope) when MAUI releases a proper unpacked publish option.
Note: Right now, I just test this approach in Windows 11.

Google can't crawl generated CSS and JS paths originating from Custom outputcache

I have set up Custom Outputcache for my website. Everything works as intended, I can see the cache folder with the binary files. When I visit the site I get the cached pages and it renders as it should.
The problem is that when I try to use Google webmaster tools to render the page, Google can't access the generated css path generated in the BundleConfig ~/bundles/styles/maincss/, the same goes to the javascript path.`. When I visit those two paths I get to see the minified JS and CSS files and the browser does render the page correctly.
This poses an issue, because now when I test the page using mobile-test tool, I get that the pages are not mobile friendly. For some reason Google can't access those paths, although when I run the web crawl in webmaster tools, it does render good for the user, but not for Google-bot.
This worked when I don't use the Custom output caching, only the default Outputcache.
Any idea how to tackle this issue.
Custom outputcache code:
Web.config:
</connectionStrings>
<appSettings>
<add key="CacheLocation" value="~/Cache"/>
</appSettings>
<system.web>
<caching>
<outputCache defaultProvider="FileCache">
<providers>
<add name="FileCache" type="ProjectX.FileCacheProvider"/>
</providers>
</outputCache>
</caching>
Caching Class:
using System;
using System.Configuration;
using System.IO;
using System.Runtime.Serialization.Formatters.Binary;
using System.Web;
using System.Web.Caching;
namespace ProjectX
{
[Serializable]
public class CacheItem
{
public object Item { get; set; }
public DateTime Expiry { get; set; }
}
public class FileCacheProvider : OutputCacheProvider
{
private string CacheLocation
{
get
{
string strCacheLocation = ConfigurationManager.AppSettings["CacheLocation"];
strCacheLocation = HttpContext.Current.Server.MapPath(strCacheLocation);
return strCacheLocation + #"\";
}
}
private string GetFullPathForKey(string key)
{
string temp = key.Replace('/', '$');
return CacheLocation + temp;
}
public override object Add(string key, object entry, DateTime utcExpiry)
{
object obj = this.Get(key);
if (obj != null)
{
return obj;
}
else
{
this.Set(key, entry, utcExpiry);
return entry;
}
}
public override void Remove(string key)
{
string filePath = GetFullPathForKey(key);
if (File.Exists(filePath))
{
File.Delete(filePath);
}
}
public override object Get(string key)
{
string filePath = GetFullPathForKey(key);
if (!File.Exists(filePath))
{
return null;
}
CacheItem item = null;
FileStream fileStream = File.OpenRead(filePath);
BinaryFormatter formatter = new BinaryFormatter();
item = (CacheItem)formatter.Deserialize(fileStream);
fileStream.Close();
if (item == null || item.Expiry <= DateTime.UtcNow)
{
Remove(key);
return null;
}
return item.Item;
}
public override void Set(string key, object entry, DateTime utcExpiry)
{
string filePath = GetFullPathForKey(key);
CacheItem item = new CacheItem { Expiry = utcExpiry, Item = entry };
FileStream fileStream = File.OpenWrite(filePath);
BinaryFormatter formatter = new BinaryFormatter();
formatter.Serialize(fileStream, item);
fileStream.Close();
}
}
}
If you wish to continue down this path (I assume you have used https://weblogs.asp.net/gunnarpeipman/asp-net-4-0-writing-custom-output-cache-providers as your starting point?), you will need to change GetFullPathForKey so that it generates valid filenames. How to remove illegal characters from path and filenames? will likely help you do that. You will also need to change your code so that it doesn't fall over if two threads try and write to the same file at the same time. Plus you really should introduce the use of a MemoryCache to avoid hitting the file system every time a Get call to your class occurs. This will be a lot of work.
I would suggest considering these NuGet packages as an alternative. They do this stuff out of the box, and are well tested:
https://www.nuget.org/packages/Microsoft.Web.RedisOutputCacheProvider/
https://www.nuget.org/packages/CacheManager.Web/
Or just whack a reverse proxy in front of the web server which assists with caching - e.g. http://mikehadlow.blogspot.com.au/2013/05/the-benefits-of-reverse-proxy.html .

Asp.NET MVC, custom TextWriterTraceListener does not create a file

For MVC application custom listener does not create a log file when initializeData="CustomWeblog.txt" parameter is used, but initializeData="d:\CustomWeblog.txt" triggers file creation. What is the reason of such behaviour? Console application generates files for all types of listeners.
Custom class:
public class CustomTextWriterTraceListener : TextWriterTraceListener
{
public CustomTextWriterTraceListener(string fileName) : base(fileName)
}
Web.config (mvc application, web.config)
<trace autoflush="true" />
<sources>
<source name="Trace">
<listeners>
<add name="TextWriterListner"
type="System.Diagnostics.TextWriterTraceListener, WebTracing" initializeData="Weblog.txt"/>
<!-- the file is created -->
<add name="CustomTextWriterListner"
type="WebTracing.CustomTextWriterTraceListener, WebTracing" initializeData="CustomWeblog.txt"/>
<!-- the file is not created in MVC application ?! -->
<add name="CustomTextWriterListnerAbsolutePath"
type="WebTracing.CustomTextWriterTraceListener, WebTracing" initializeData="d:\CustomWeblog.txt"/>
<!-- the file is created -->
</listeners>
</source>
</sources>
Cutom listener does not create a log file.
Caller:
TraceSource obj = new TraceSource("Trace", SourceLevels.All);
obj.TraceEvent(TraceEventType.Critical,0,"This is a critical message");
I have tried to add some extra configuration: from this blog and this one. But there is no success. Should I provide a absolute path? Is there any workaround by creating a separate assembly for custom listener?
I was trying to create my own rolling text writer trace listener when I was encountering the same issue you described. Long story short, after all the running around here is what I came up with.
public class RollingTextWriterTraceListener : TextWriterTraceListener {
string fileName;
private static string[] _supportedAttributes = new string[]
{
"template", "Template",
"convertWriteToEvent", "ConvertWriteToEvent",
"addtoarchive","addToArchive","AddToArchive",
};
public RollingTextWriterTraceListener(string fileName)
: base() {
this.fileName = fileName;
}
/// <summary>
/// This makes sure that the writer exists to be written to.
/// </summary>
private void ensureWriter() {
//Resolve file name given. relative paths (if present) are resolved to full paths.
// Also allows for paths like this: initializeData="~/Logs/{ApplicationName}_{DateTime:yyyy-MM-dd}.log"
var logFileFullPath = ServerPathUtility.ResolvePhysicalPath(fileName);
var writer = base.Writer;
if (writer == null && createWriter(logFileFullPath)) {
writer = base.Writer;
}
if (!File.Exists(logFileFullPath)) {
if (writer != null) {
try {
writer.Flush();
writer.Close();
writer.Dispose();
} catch (ObjectDisposedException) { }
}
createWriter(logFileFullPath);
}
//Custom code to package the previous log file(s) into a zip file.
if (AddToArchive) {
TextFileArchiveHelper.Archive(logFileFullPath);
}
}
bool createWriter(string logFileFullPath) {
try {
logFileFullPath = ServerPathUtility.ResolveOrCreatePath(logFileFullPath);
var writer = new StreamWriter(logFileFullPath, true);
base.Writer = writer;
return true;
} catch (IOException) {
//locked as already in use
return false;
} catch (UnauthorizedAccessException) {
//ERROR_ACCESS_DENIED, mostly ACL issues
return false;
}
}
/// <summary>
/// Get the add to archive flag
/// </summary>
public bool AddToArchive {
get {
// Default behaviour is not to add to archive.
var addToArchive = false;
var key = Attributes.Keys.Cast<string>().
FirstOrDefault(s => string.Equals(s, "addtoarchive", StringComparison.InvariantCultureIgnoreCase));
if (!string.IsNullOrWhiteSpace(key)) {
bool.TryParse(Attributes[key], out addToArchive);
}
return addToArchive;
}
}
#region Overrides
/// <summary>
/// Allowed attributes for this trace listener.
/// </summary>
protected override string[] GetSupportedAttributes() {
return _supportedAttributes;
}
public override void Flush() {
ensureWriter();
base.Flush();
}
public override void Write(string message) {
ensureWriter();
base.Write(message);
}
public override void WriteLine(string message) {
ensureWriter();
base.WriteLine(message);
}
#endregion
}
UPDATE: Here is the utility class I wrote for resolving paths.
public static class ServerPathUtility {
public static string ResolveOrCreatePath(string pathToReplace) {
string rootedFileName = ResolvePhysicalPath(pathToReplace);
FileInfo fi = new FileInfo(rootedFileName);
try {
DirectoryInfo di = new DirectoryInfo(fi.DirectoryName);
if (!di.Exists) {
di.Create();
}
if (!fi.Exists) {
fi.CreateText().Close();
}
} catch {
// NO-OP
// TODO: Review what should be done here.
}
return fi.FullName;
}
public static string ResolvePhysicalPath(string pathToReplace) {
string rootedPath = ResolveFormat(pathToReplace);
if (rootedPath.StartsWith("~") || rootedPath.StartsWith("/")) {
rootedPath = System.Web.Hosting.HostingEnvironment.MapPath(rootedPath);
} else if (!Path.IsPathRooted(rootedPath)) {
rootedPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, rootedPath);
}
return rootedPath;
}
public static string ResolveFormat(string format) {
string result = format;
try {
result = ExpandApplicationVariables(format);
} catch (System.Security.SecurityException) {
// Log?
}
try {
string variables = Environment.ExpandEnvironmentVariables(result);
// If an Environment Variable is not found then remove any invalid tokens
Regex filter = new Regex("%(.*?)%", RegexOptions.IgnoreCase | RegexOptions.IgnorePatternWhitespace);
string filePath = filter.Replace(variables, "");
if (Path.GetDirectoryName(filePath) == null) {
filePath = Path.GetFileName(filePath);
}
result = filePath;
} catch (System.Security.SecurityException) {
// Log?
}
return result;
}
public static string ExpandApplicationVariables(string input) {
var filter = new Regex("{(.*?)}", RegexOptions.IgnoreCase | RegexOptions.IgnorePatternWhitespace);
var result = filter.Replace(input, evaluateMatch());
return result;
}
private static MatchEvaluator evaluateMatch() {
return match => {
var variableName = match.Value;
var value = GetApplicationVariable(variableName);
return value;
};
}
public static string GetApplicationVariable(string variable) {
string value = string.Empty;
variable = variable.Replace("{", "").Replace("}", "");
var parts = variable.Split(new[] { ':' }, StringSplitOptions.RemoveEmptyEntries);
variable = parts[0];
var parameter = string.Empty;
if (parts.Length > 1) {
parameter = string.Join("", parts.Skip(1));
}
Func<string, string> resolve = null;
value = VariableResolutionStrategies.TryGetValue(variable.ToUpperInvariant(), out resolve) && resolve != null
? resolve(parameter)
: string.Empty;
return value;
}
public static readonly IDictionary<string, Func<string, string>> VariableResolutionStrategies =
new Dictionary<string, Func<string, string>> {
{"MACHINENAME", p => Environment.MachineName },
{"APPDOMAIN", p => AppDomain.CurrentDomain.FriendlyName },
{"DATETIME", getDate},
{"DATE", getDate},
{"UTCDATETIME", getUtcDate},
{"UTCDATE", getUtcDate},
};
static string getDate(string format = "yyyy-MM-dd") {
var value = string.Empty;
if (string.IsNullOrWhiteSpace(format))
format = "yyyy-MM-dd";
value = DateTime.Now.ToString(format);
return value;
}
static string getUtcDate(string format = "yyyy-MM-dd") {
var value = string.Empty;
if (string.IsNullOrWhiteSpace(format))
format = "yyyy-MM-dd";
value = DateTime.Now.ToString(format);
return value;
}
}
So this utility class allows me to resolve relative paths and also customize formats. For example, if you looked at the code you would have seen that application name ApplicationName variable does not exist in this path
"~/Logs/{ApplicationName}_{DateTime:yyyy-MM-dd}.log"
I am able to configure that in the startup of the application along with any other variables I want to add like so
public partial class Startup {
public void Configuration(IAppBuilder app) {
//... Code removed for brevity
// Add APPLICATIONNAME name to path Utility
ServerPathUtility.VariableResolutionStrategies["APPLICATIONNAME"] = p => {
var assembly = System.Reflection.Assembly.GetExecutingAssembly();
if (assembly != null)
return assembly.GetName().Name;
return string.Empty;
};
}
}
Ok, finally, I have switched investigation to the way listener paths are generated. What I have noticed on debugging is that source listeners list contain different paths.
System.Diagnostics.TextWriterTraceListener listener object has a full path generated;
WebTracing.CustomTextWriterTraceListener has only file name. There is no generated errors.
The different values as caused by the reason that custom listened swallowed UnauthorisedAccessException exception so that the application continues working without informing us about permissions issues.
But what is the place of storing Custom listener log files? Are they
generated or not?
The following link to the TextWriterTraceListener source code helped me to figure out the path. The following code:
//initializeData="CustomWeblog.txt", so fileName == "CustomWeblog.txt" here
string fullPath = Path.GetFullPath(fileName);
string dirPath = Path.GetDirectoryName(fullPath);
string fileNameOnly = Path.GetFileName(fullPath);
Actual storage path depends on Project > Properties > Web > Server: IIS Express:
c:\Program Files (x86)\IIS Express\CustomWeblog.txt
All the time I was debudding the MVC application (as an administrator: vs run as administrator) log files were correctly generated in that folder. When I am running VS without administrator permissions custom listeners do not create files at all.
As it was mentined above, I executed the source code listener and found that catch(UnauthorisedAccessException) { break; } is triggered on new StreamWriter(...) constructor call.
Why is access to the path denied? SO link
What are all the user accounts for IIS/ASP.NET and how do they differ? (In Practice section of the answer) SO link
Awesome video tutorial: Application pools in IIS on IIS and Application Pool configuration by Pragime Tech.
As another workaround you can declare the whole path in initializeData="d:\CustomWeblog.txt" attribute. But keep in mind that you have to have the proper permissions.

Programmatically compile typescript in C#?

I'm trying to write a function in C# that takes in a string containing typescript code and returns a string containing JavaScript code. Is there a library function for this?
You can use Process to invoke the compiler, specify --out file.js to a temporary folder and read the contents of the compiled file.
I made a little app to do that:
Usage
TypeScriptCompiler.Compile(#"C:\tmp\test.ts");
To get the JS string
string javascriptSource = File.ReadAllText(#"C:\tmp\test.js");
Full source with example and comments:
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
namespace ConsoleApplication1
{
class Program
{
static void Main(string[] args)
{
try
{
// compiles a TS file
TypeScriptCompiler.Compile(#"C:\tmp\test.ts");
// if no errors were found, read the contents of the compile file
string javascriptSource = File.ReadAllText(#"C:\tmp\test.js");
}
catch (InvalidTypeScriptFileException ex)
{
// there was a compiler error, show the compiler output
Console.WriteLine(ex.Message);
}
Console.ReadKey();
}
}
public static class TypeScriptCompiler
{
// helper class to add parameters to the compiler
public class Options
{
private static Options #default;
public static Options Default
{
get
{
if (#default == null)
#default = new Options();
return #default;
}
}
public enum Version
{
ES5,
ES3,
}
public bool EmitComments { get; set; }
public bool GenerateDeclaration { get; set; }
public bool GenerateSourceMaps { get; set; }
public string OutPath { get; set; }
public Version TargetVersion { get; set; }
public Options() { }
public Options(bool emitComments = false
, bool generateDeclaration = false
, bool generateSourceMaps = false
, string outPath = null
, Version targetVersion = Version.ES5)
{
EmitComments = emitComments;
GenerateDeclaration = generateDeclaration;
GenerateSourceMaps = generateSourceMaps;
OutPath = outPath;
TargetVersion = targetVersion;
}
}
public static void Compile(string tsPath, Options options = null)
{
if (options == null)
options = Options.Default;
var d = new Dictionary<string,string>();
if (options.EmitComments)
d.Add("-c", null);
if (options.GenerateDeclaration)
d.Add("-d", null);
if (options.GenerateSourceMaps)
d.Add("--sourcemap", null);
if (!String.IsNullOrEmpty(options.OutPath))
d.Add("--out", options.OutPath);
d.Add("--target", options.TargetVersion.ToString());
// this will invoke `tsc` passing the TS path and other
// parameters defined in Options parameter
Process p = new Process();
ProcessStartInfo psi = new ProcessStartInfo("tsc", tsPath + " " + String.Join(" ", d.Select(o => o.Key + " " + o.Value)));
// run without showing console windows
psi.CreateNoWindow = true;
psi.UseShellExecute = false;
// redirects the compiler error output, so we can read
// and display errors if any
psi.RedirectStandardError = true;
p.StartInfo = psi;
p.Start();
// reads the error output
var msg = p.StandardError.ReadToEnd();
// make sure it finished executing before proceeding
p.WaitForExit();
// if there were errors, throw an exception
if (!String.IsNullOrEmpty(msg))
throw new InvalidTypeScriptFileException(msg);
}
}
public class InvalidTypeScriptFileException : Exception
{
public InvalidTypeScriptFileException() : base()
{
}
public InvalidTypeScriptFileException(string message) : base(message)
{
}
}
}
Perhaps you could use a JavaScript interpreter like JavaScriptDotNet to run the typescript compiler tsc.js from C#.
Something like:
string tscJs = File.ReadAllText("tsc.js");
using (var context = new JavascriptContext())
{
// Some trivial typescript:
var typescriptSource = "window.alert('hello world!');";
context.SetParameter("typescriptSource", typescriptSource);
context.SetParameter("result", "");
// Build some js to execute:
string script = tscJs + #"
result = TypeScript.compile(""typescriptSource"")";
// Execute the js
context.Run(script);
// Retrieve the result (which should be the compiled JS)
var js = context.GetParameter("result");
Assert.AreEqual(typescriptSource, js);
}
Obviously that code would need some serious work. If this did turn out to be feasible, I'd certainly be interested in the result.
You'd also probably want to modify tsc so that it could operate on strings in memory rather than requiring file IO.
The TypeScript compiler file officially runs on either node.js or Windows Script Host - it is written in TypeScript itself (and transpiled to JavaScript). It requires a script host that can access the file system.
So essentially, you can run TypeScript from any language as long as you can wrap it in a script engine that supports the file system operations required.
If you wanted to compile TypeScript to JavaScript purely in C#, you would end up writing a C# clone of the compiler.

c# windows store app granting capabilities to used dll-s

I'd like to use TagLib in my Windows Store project.
The TagLib is imported as reference, with its dll ( taglib-sharp.dll )
I can access any file in my music folder since it is checked in the capabilities. However, when I call
TagLib.File file = TagLib.File.Create(soundFilePath, TagLib.ReadStyle.None);
it throws the following error:
System.UnauthorizedAccessException was unhandled by user code
HResult=-2147024891
Message=Access to the path 'C:\Users\Gabor\Music\_FIFA 2010 soundtracks\01. Meine Stadt - Auletta.mp3' is denied.
Source=taglib-sharp
StackTrace:
at TagLib.File.Create(IFileAbstraction abstraction, String mimetype, ReadStyle propertiesStyle)
at TagLib.File.Create(String path, String mimetype, ReadStyle propertiesStyle)
at TagLib.File.Create(String path, ReadStyle propertiesStyle)
You can use TagLibSharp to load tags by creating a StreamFileAbstraction and passing that to File.Create. This won't use any banned APIs.
public void ExampleCall(StorageFile storageFile)
{
IRandomAccessStreamWithContentType f = await storageFile.OpenReadAsync();
var file = File.Create(new StreamFileAbstraction(storageFile.Name, f.AsStream()));
}
public class StreamFileAbstraction : File.IFileAbstraction
{
public StreamFileAbstraction(string name, Stream stream)
{
Name = name;
ReadStream = stream;
WriteStream = stream;
}
public void CloseStream(Stream stream)
{
stream.Flush();
}
public string Name { get; private set; }
public Stream ReadStream { get; private set; }
public Stream WriteStream { get; private set; }
}
couldn't do it this way. used MusicProperties instead, and then used lastfm api to get the needed info.. shame its so difficult in Win8 c# what is easy in normal C#
For WinRT you need next:
var task = await StorageFile.GetFileFromApplicationUriAsync(uri);
var stream = await task.OpenStreamForReadAsync();
using (var info = File.Create(new StreamFileAbstraction(Path.GetFileName(uri.LocalPath), stream, stream)))
{
Album = info.Tag.Album;
Comment = info.Tag.Comment;
// more properties
}
and you need add NuGet Packages - TagLib# Portable

Categories

Resources