I have a situation where I would like to present a list of the "available languages" for my application (which, incidentally, is an ASP .NET MVC 3 application if that makes any odds). I thought that I could automatically get this list somehow since it should just be the resx files that are included in the build (I don't need to support English UK, German Austria or anything, just English or German) and I came up with a scheme that I will present below (implemented as a singleton since it is a bit of an intensive approach).
The problem is that on some machines it returns "Arabic" even though I have no such resource and on mine (since I installed VS 2012) it returns all of them (this makes more sense to me than returning just the two real cultures plus Arabic but it seems that the ResourceManager just wasn't designed to let me get at this information so I probably should not complain). Here is the scheme...
(I have a Strings.resx and a Strings.de.resx file)
IEnumerable<CultureInfo> cultures =
CultureInfo.GetCultures(CultureTypes.NeutralCultures)
.Where(c =>
{
// Exclude the invariant culture and then load up
// an arbitrary string so the resource manager
// loads a resource set, then get the set for the
// current culture specifically and it is, sometimes
// (I thought always but I was wrong) null if no
// set exists
if (c.LCID == CultureInfo.InvariantCulture.LCID)
return false;
var rm = Strings.ResourceManager;
rm.GetString("HELLO", c);
return rm.GetResourceSet(c, false, false) != null;
});
So then I thought, well, I could do this based on whether the language-specific directory exists like so:
var neutralCulture = new[]
{
CultureInfo
.CreateSpecificCulture(((NeutralResourcesLanguageAttribute)
Assembly
.GetExecutingAssembly()
.GetCustomAttributes(
typeof (NeutralResourcesLanguageAttribute),
false)[0])
.CultureName)
};
IEnumerable<CultureInfo> cultures =
CultureInfo.GetCultures(CultureTypes.NeutralCultures)
.Where(c => Directory.Exists(c.TwoLetterISOLanguageName))
.Union(neutralCulture);
This "works" (in so much as it returns English and German) but I think it is not a very stable approach being as it is prone to random problems like someone creating a folder and throwing it all out of whack. I can probably alleviate these issues with some more judicious checks (the where clause is crying out for more sophistication) but and here is the question (finally)...
Right now I am thinking of just going with a config file and keeping it totally simple since I do not really like where I have got to but is there a better way to do this (or: can it be done automatically in a safe way)?
I like your second approach for automatic detection. I would add though that you should only do this once (on application start or as part of a static constructor) and make it static instead of computing it every time you request the supported culture info.
I think the config approach would work as well, though it's not really automatic. My only thought for that case is that if you are localizing for a language, it's not something that's going to sneak into your application under the radar. That being said, adding a config value at that point seems like an easy thing to do (or to forget to do).
I'm not aware of anything built into the .NET Framework to give you this information with a method call.
I found no valid and reliable way of doing this.
In each of my assemblies I write a code like this that references the supported cultures. That's all we can do. You have to remember to update this array when you add a localization.
internal static class ThisAssembly
{
static readonly string[] SupportedCultures = new string[] { "en-US", "de-DE", };
}
I also tried to:
enumerate the loaded sattelite assemblies: there is no method for that;
get the value of ResourceManager._resourceSets: the dictionary of loaded sets gives correct and invalid values.
But to no avail.
Related
I have an application that allows users to write c-sharp code that gets saved as a class library for being called later.
A new requirement has been established that some namespaces (and the methods they contain and any variables or methods with their return types) are not allowed anymore. So I need to analyze the code and alert the user to any forbidden namespaces in their code so they can remove them.
Using Roslyn, I can access the InvocationExpressionSyntax nodes for the method calls. From that I then get the symbol info by calling var mySymbol = mySemanticModel.GetSymbolInfo(myInvocationExpressionSyntaxNode).Symbol.
Then calling mySymbol.ContainingType.ToDisplayString() returns the namespace type of the call.
However, it seems not all called methods have symbol information in Roslyn. For example, System.Math.Sqrt() has symbol information, so from that I can get the containing namespace of System.Math. On the other hand System.Net.WebRequest.Create() or System.Diagnostics.Process.Start() do not. How do I get System.Net.WebRequest or System.Dignostics.Process from those nodes? I can clearly see them using QuickWatch.
For example, the System.Diagnostics.Process.Start() node itself shows the following value in QuickWatch:
InvocationExpressionSyntax InvocationExpression System.Diagnostics.Process.Start("CMD.exe","")
And the node's expression has this value:
MemberAccessExpressionSyntax SimpleMemberAccessExpression System.Diagnostics.Process.Start
So obviously the namespace is there in the value itself. But the Symbol from the SymbolInfo and the Type from TypeInfo are both null.
Edit
In regards to my compilation, the C# Roslyn tools are set up as follows (we are supposed to support VB as well, hence the properties are interfaced):
private class CSharpRoslynTools : IRoslynTools
{
public CompilationUnitSyntax SyntaxTreeRoot { get; }
public SemanticModel SemanticModel { get; }
public CSharpRoslynTools(string code)
{
var mscorlib = MetadataReference.CreateFromFile(typeof(object).Assembly.Location);
var syntaxTree = CSharpSyntaxTree.ParseText(code);
var compilation = CSharpCompilation.Create(
"MyCompilation",
syntaxTrees: new[] { syntaxTree },
references: new[]
{
mscorlib
});
this.SemanticModel = compilation.GetSemanticModel(syntaxTree);
this.SyntaxTreeRoot = (CompilationUnitSyntax)syntaxTree.GetRoot();
}
}
One thing I did come to realize is that System Diagnostics isn't part of the mscorlib. Could that be why the symbol information is missing?
Honestly, I kind of view this as a bit of a waste of my time because the scripts were designed to be run in a WinForms desktop application that's probably 15 years old at this point. But then they decided to this desktop application needed to move to Citrix Cloud for certain customers. And as a result, we have to lock out anything that can access the filesystem if it's not an admin logged into the application. So we have this giant potential security hole with these scripts. The chance of someone getting access to the application and exploiting any of this is slim, though.
I pushed for a blacklist, which would be easy enough to do with a simple string search of the code. They want a whitelist which requires full out parsing of the symbols.
However, it seems not all called methods have symbol information in Roslyn.
This probably indicates that something went wrong with how you got your Compilation, and you should attempt to investigate that directly. Don't attempt to deal with it downstream. (Software: garbage in, garbage out!)
On the other hand System.Net.WebRequest.Create() or System.Diagnostics.Process.Start() do not. How do I get System.Net.WebRequest or System.Dignostics.Process from those nodes? I can clearly see them using QuickWatch.
Keep in mind that from the perspective of syntax only, System.Net.WebRequest.Create() could be:
A Create method on the WebRequest type that's in System.Net
A Create method on on the WebRequest type, which is a nested class of the Net type, in the System namespace
A Create method on the WebRequest type that's in MyApp.System.Net.WebRequest, because we of course don't require fully namespace names and if you decide to make a System namespace inside your MyApp, that could potentially work!
One thing I did come to realize is that System Diagnostics isn't part of the mscorlib. Could that be why the symbol information is missing?
Yep; we're only going to reference the assemblies you give us. It's up to you to know your context and if other references are included in what that code can reference, then you should include them in your production of the Compilation.
I pushed for a blacklist, which would be easy enough to do with a simple string search of the code. They want a whitelist which requires full out parsing of the symbols.
From a security perspective they may be right -- it's very difficult to block from a string search. See some thoughts at https://stackoverflow.com/a/66555319/972216 for how difficult that can be.
This is either too clever by half or not clever enough by 62.784%. For odd reasons, our product management decided they wanted to rename some of our enum values and how they serialize. After a couple of builds they decided they wanted it to be backwards compatible - i.e. read and process old serializations to the new values.
I thought I'd try declaring the new name in the enum first, then declaring OldName = NewName just after as a synonym, hoping that the equivalency would get worked out in the wash in favor of the first declaration and it would automagically translate old to new with Enum.Parse.
I worked up a little sample app like so:
public enum syns
{
Zero,
One,
Two,
Three = Two,
Four,
};
public string SynTest()
{
string result;
syns synTest2 = (syns)syns.Two, synTest3;
bool okay = Enum.TryParse<syns>("Three", out synTest3);
result = (okay).ToString() + "," + (synTest2 == synTest3).ToString() + "," + synTest2.ToString() + "," + synTest3.ToString() + ",";
synTest3 = (syns)Enum.Parse(typeof(syns), "Three");
result += synTest3.ToString();
return result;
}
and got what I wanted. The old name "Three" parsed, and when the parsed value was evaluated it produced the new name Two every which way. Yay.
So I used the technique in our actual code (a much longer enum, with lots of app-specific names/values, etc). Worked on my machine, so I checked it in.
The problem we're having is that there doesn't appear to be any consistency in how this is handled. The build got to QA, and (the equivalent of) "Three" parsed and stayed as Three throughout (old name) rather than evaluating to the new version Two.
After pulling on this thread for a while, it seems like about 50% of the machines we've tried it on process it the way my machine did (new value prevailing) and 50% skew to the old name.
I tried pulling all of the test cases into a test.aspx page, and my test.aspx page behaves differently than the underlying assemblies. And in the test.aspx page, the enum above behaves differently from the actual enum from our code base.
Any hints as to what determines how evaluations of synonym enums lean?
Any ideas why the same assemblies would change behavior machine to machine? All the machines are set to target framework 4.5.1, and at least on the couple I compared it seemed they were at the same patch level.
Even on a machine where I got the desired behavior, pulling the same declaration into a test.aspx changed the behavior, so I was wondering if this is fickle on a file-by-file basis.
This is "clearly" documented under Enum.ToString:
Notes to Callers
If multiple enumeration members have the same underlying value and you attempt to retrieve the string representation of an enumeration member's name based on its underlying value, your code should not make any assumptions about which name the method will return.
(my emphasis)
This is documented to have unspecified behavior. There may in fact be a observable (seemingly) consistent behavior on one machine depending on the .NET runtime version, .NET hotfixes installed etc. etc. but you cannot make any guarantees that it will continue to behave like that tomorrow or next week.
In short, you cannot do it this way, you need to find another way to handle the aliasing.
Let's say I have:
#{
var str= "DateTime.Now";
}
I want to process this string as a c# code
#Html.Raw(App.ProcessAsCode(str));
The output should be the current date time.
Final Edit:
Based on further information - if the goal here is to simply have a formatting engine there are lots of options out there. One such option is based around the .liquid syntax from shopify (see here). You can find a .NET port of this on gitHub here: https://github.com/formosatek/dotliquid/. The main purpose of this is to turn something like:
<h2>{{product.name}}</h2>
Into something like:
<h2>Beef Jerky</h2>
I would strongly recommend reading more about the liquid engine and syntax and I believe this will lead you in the right direction. Best of luck!
Initial Answer
This is definitely possible - although as others have said you will want to be careful in what you do. Using C# the key to compiling and running code generically is the "CSharpCodeProvider" class. Here is a brief example of how that looks:
string[] references = { "System.dll" };
CompilerParams.ReferencedAssemblies.AddRange(references);
var provider = new CSharpCodeProvider();
CompilerResults compile = provider.CompileAssemblyFromSource(CompilerParams, formattedCode);
In this example, "formattedCode" is a string with the C# code. Any references must be manually added. For the full example see this stack question (How to get a Type from a C# type name string?).
NOTE -- If all you are looking to do here is a format string or something simple like that you might have the user pass in a .NET format string (eg "MM/dd/yyyy"), then use that in a call to the "ToString" method. That would provide the user some configurability, while still making sure your system stays secure. In general running code on a server that hasn't been properly checked/escaped is really dangerous!
Reference - For your reference, the current msdn page for CSharpCodeProvider also has some examples.
Another option would be using a dynamic language such as IronRuby or IronPython.
Before I start, Let me clear up any confusion before anyone suggests or asks the question..
This Question is related to "Windows Mobile 6 Professional", NOT Windows Phone 7 and it can't be ported to windows phone 7 as it has to go on to an older device.
Now, with that out of the way...
I'm currently trying to port a C# library that I have the source for to run on a windows mobile 6 device, Iv'e coded these things it seems like for ever, but there's one thing Iv'e never had to deal with until now and that's reflection.
Now everyone know that the .NET compact framework does have some limitations, and it appears that lack of support for a lot of the methods and properties in the 'System.Reflection' namespace is one of them.
The actual desktop version of the library is set to target .NET V2.0, and I have devices that are running .NET 3.5 SP1 so for the most part Iv'e had very little problem in getting things to work, a cannot however seem to find a sensible to get the following 2 chunks of code working:
var a = AppDomain.CurrentDomain**.GetAssemblies**();
foreach (var assembly in a)
{
if (assembly is System.Reflection**.Emit.**AssemblyBuilder) continue;
if (assembly**.GetType().**FullName == "System.Reflection.Emit.InternalAssemblyBuilder") continue;
if (assembly**.GlobalAssemblyCache** && assembly**.CodeBase** == Assembly.GetExecutingAssembly()**.CodeBase**) continue;
foreach (var t in GetLoadableTypes(assembly))
{
if (t.IsInterface) continue;
if (t.IsAbstract) continue;
if (t.IsNotPublic) continue;
if (!typeof(IGeometryServices).IsAssignableFrom(t)) continue;
var constuctors = t.GetConstructors();
foreach (var constructorInfo in constuctors)
{
if (constructorInfo.IsPublic && constructorInfo.GetParameters().Length == 0)
return (IGeometryServices)Activator.CreateInstance(t);
}
}
}
And
catch (**ReflectionTypeLoadException** ex)
{
var types = ex**.Types**;
IList<Type> list = new List<Type>(types**.Length**);
foreach (var t in types)
if (t != null && t**.IsPublic**)
list.Add(t);
return list;
}
Specifically, those items in bold in the above code are the methods and properties that don't appear to be present in the compact framework, and after spending quite a chunk of time with intellisense and the object browser, Iv'e not found anything that returns (or makes available) the same types.
My question then is as follows:
Does anyone have any experience of using reflection in the Compact .NET framework, and can suggest how this code can be made to work as expected, or am I going to have to start writing custom stubs and functionality to replace the missing methods.
I know there is some reflection capabilities on the framework, so I'm sure there must be an equivalent way of achieving it.
Just on a final note, for anyone who may recognise the code. YES it is from the .NET topology suite, and yes it is that library I'm trying to build a WM6 version of, so if you know of anyone that has already done it please do put a comment on to that effect, and I'll go take a look at the easier path :-)
======================================================================
Update after posting
It appears 'Bold' text doesn't work in code snippets, so those methods / properties in the above code that are surrounded by ** are the parts supposed to be in bold.
So after studying some older builds and an experimental Silverlight build (Which apparently has a lot of the same limitations as Windows Mobile / CE)
I figured out how to make the magic work.
The first part was to fill the array with 'Assembly' structures representing the assemblies to search. Originally it was:
var a = AppDomain.CurrentDomain.GetAssemblies();
but since GetAssemblies doesn't exist in WM then the quickest way is to use the following:
var a = new[] {Assembly.GetCallingAssembly(), Assembly.GetExecutingAssembly()};
Now in my case this meant that I got two identical assemblies, but if I was calling an Assembly from my main exe which in turn used GeoAPI then 2 different assemblies would be shown here.
The next challenge was the "stripping out" of assemblies we didn't need to check:
if (assembly is System.Reflection.Emit.AssemblyBuilder) continue;
if (assembly.GetType().FullName == System.Reflection.Emit.InternalAssemblyBuilder") continue;
if (assembly.GlobalAssemblyCache && assembly.CodeBase == Assembly.GetExecutingAssembly().CodeBase) continue;
The first and second instances never crop up in WM6 so it's safe to just comment them out. The second one does work, but since you'll never see any assemblies with the given check name (Due to it being missing) on WM6 then again, you should be safe to comment it out. I didn't comment it out, but it also never got triggered as true.
The final part (at least as far as the original puzzle went anyway) was this:
foreach (var t in GetLoadableTypes(assembly))
{
if (t.IsInterface) continue;
if (t.IsAbstract) continue;
if (t.IsNotPublic) continue;
if (!typeof(IGeometryServices).IsAssignableFrom(t)) continue;
In my original attempt 'isInterface' & 'isNotPublic' where missing, however once I managed to fix up the contents of the 'var a' variable above with the data type it expected, everything started to work ok with nothing missing.
The final question is? Did this solve everything? Well not quite....
It turns out that the whole purpose of the 'ReflectInstance' method in GeoAPI was to find a user defined 'GeometryFactory' using an 'IGeometryServices' interface.
Since I was only reflecting over the assembly I was calling from (var a above) then the 'NetTopologySuite' (Where the geometry factory is defined) was not added to the select list (Obviously CurrentDomain.GetAssemblies includes this)
The net result was I still ended up hitting the exception at the end as no Assemblies of the correct type could be located.
It turns out however, that in the 'Geometry' constructor in GeoAPI has an overload that allows you to pass in a GeometryFactory, when you do this it completely ignores the ReflectInstance method and just uses what it's told.
or to put it another way , I never had to do any of this in the first place, I could have just set the method to return 'null' and passed in the geometry factory I wanted to use.
Anyway, if anyone is interested I now have a working copy of:
GeoAPI.NET
NetTopologySuite
Wintellect.PowerCollections
Built and working fine under Windows Mobile 6 and Windows CE with .NET CF 3.5.
I am trying to construct a serialisation system for our application that must be able to handle inheritance. To further complicate matters the application is extensible so types might are very unlikely to be known at compile time.
I have read through a previous stackoverflow question that was answered and that helped me get a long way to my goal but I have hit a stumbling block that is probably more of a lack of understanding than any real issue!
So this is the code that I have currently...
public interface IBaseFrame
{
}
public class BasicDataFrame : IBaseFrame
{
}
public class AnotherFrame : BasicDataFrame
{
}
.
.
.
.
RuntimeTypeModel model = TypeModel.Create();
MetaType baseType = model.Add(typeof(IBaseFrame), true);
MetaType basicFrameType = model.Add(typeof(BasicDataFrame),true);
baseType.AddSubType(7, typeof(BasicDataFrame));
model.Add(typeof(AnotherFrame), true);
basicFrameType.AddSubType(8, typeof(AnotherFrame));
Now this code works correctly (as far as I can tell!) and all is good in the world... My concern is where the values 7 and 8 are used in the code above for the fieldNumber argument of the AddSubType method.
As the plugins are enumerated I am registering their frame types with the SerialisationManager and I add them to the model with an incrementing counter for the fieldNumber (I start it at an arbitrarily large number so I don't have to worry about extending the base classes in the future).
I am worried that if the plugins are enumerated in a different order or new ones are added (or removed) then the auto generated fieldNumber is going to be different for the different sub types that will then cause issues with sharing log files between users (who may have different plugins and therefore subtypes) or even files on the same system when a plugin may have been removed.
Is there any cunning way of dealing with this kind of inheritance automatically and without introducing compatibility issues in the future or when plugins change or do I need to come up with a mechanism where these fieldNumbers can be guaranteed to remain contant across all installations?
Any help or advice that can be given would be greatly appreciated!
No magic here; the numbers are essentially part of the contract, and it is important that they can be reliably reproduced if you want to deserialize data you stored previously. If the data can't be known at compile-time, maybe configuration or some external registry of types to field numbers may help. Your concerns are accurate.