How can I get location of an .aspx file using Reflection - c#

I'm searching my assembly (using System.Reflection) for classes that are inherited from a specific class (System.Web.UI.Page).
All these inherited classes have pages in my web.
How can I read .aspx file path too (relative to web root)?
I know I can read this path from .pdb file, however this file does not exists in live server.
Edit
to avoid confusion, here is a sample of my code. doSomeProcess is not exact process. in fact it receives the class itself and analyzes it too. however it is not important for this code sample.
using System;
using System.IO;
using System.Reflection;
namespace ... {
public class FixPaths: System.Web.UI.Page {
void SearhcAssembly(){
Assembly library = Assembly.GetExecutingAssembly();
string FileName = Path.GetFileName(library.Location);
foreach (Type type in library.GetTypes()) {
if (FileName == type.Module.Name) {
if (type.BaseType != null && (type.BaseType.FullName == "System.Web.UI.Page")) {
string NameSpace = type.Namespace;
string ClassName = type.Name;
// -- This need to be changed --
string PageUrl = "/" + type.Namespace.ToLower().Replace(".", "/") + "/" + type.Name + ".aspx";
// -------------------------------------
doSomeProcess(NameSpace, ClassName, PageUrl);
}
}
}
}
}
}

Bob, unfortunately this is not a proper solution. However, as far as we discussed on the comments, it may be the nearest we can get to it.
By reflection, get all the types at your assembly that inherit from Page.
Using a recursive I/O search, look for all *.aspx files under your root folder and keep them in a list (so you can retrieve their full path and so on
Compare the types you found with the files you fetched. This way you can report Pages (classes) that aren't on the right file and ASPX that aren't on the right place.
I couldn't find a relationship between the type and it's source file. Furthermore, we are talking about Pages (declared at the assembly) and ASPX files, which aren't related at all.
If anybody has a better solution, I'd like to take a look at it as well.
Regards

The page's relative path from the root is in the AppRelativeTemplateSourceDirectory property (on the page). Is that what you are after?

Related

Search priority for views using EmbeddedFileProvider

I'm have this code pulling views embedded as resource in my referenced assembly:
services.Configure<RazorViewEngineOptions>(options =>
{
options.FileProviders.Add(new EmbeddedFileProvider(typeof(SomeTypeInMyAssembly).GetTypeInfo().Assembly));
});
It successfully finds views in the embedded location (Views\Shared\Components\ViewComponentName\Default.cshtml). I need it to FIRST search for the files in the current project BEFORE looking at any assemblies, this way I can create defaults in an assembly, and allow "overrides" in the main project (same path). Anyone have any ideas how this can be done? I'm still trying to look through the source to figure this out.
And no, ViewLocationExpander is not the answer. I need to use the exact same path and file names, thanks.
Turns out there are TWO options:
Add
options.FileProviders.Add(HostingEnvironment.ContentRootFileProvider);
just before EmbeddedFileProvider. (HostingEnvironment was simply taken
from the Startup() constructor and stored locally in the Startup class. The physical file on disk (could also be in the cache) will be found before the assembly version.
Wrap EmbeddedFileProvider in your own type (implement IFileProvider) and pass in IHostingEnvironment. The GetFileInfo() method is
called while trying to locate files. The
IHostingEnvironment instance is used to detect physical
files from the root content path, and returns NotFoundFileInfo if a local file exists:
public virtual IFileInfo GetFileInfo(string subpath)
{
if (_HostingEnvironment != null)
{
var filepath = Path.Combine(_HostingEnvironment.ContentRootPath, subpath.TrimStart('/'));
if (File.Exists(filepath))
return new NotFoundFileInfo(filepath);
}
return _EmbeddedFileProvider.GetFileInfo(subpath)
}
and add it to Startup.ConfigureServices():
services.Configure<RazorViewEngineOptions>(options =>
{
options.FileProviders.Add(new MyEmbeddedFileProvider(typeof(SomeTypeInTheTargetAssembly).GetTypeInfo().Assembly, HostingEnvironment));
});
}

Get the path from a SpecFlow feature file in a Step Definition

Is it possible to retrieve the path of a SpecFlow feature file during runtime in a Step Definition?
Snippet:
[Given(#"Some given statement")]
public void GivenSomeGivenStatement() {
var featureFilePath = // retrieve the path of the feature file
// that executes this step.
}
Context:
We do testing on databases and queries. The source data is created in Excel files and .SQL files (for check queries). These source data are large datasets, not feasible to put into the feature files itself or use the SpecFlow.Plus.Excel extension.
To keep the data close to the feature file, we want to have this data in the same folder as the feature file itself. To achieve this, we need the path to this feature file, so we also have the path to the testdata.
Here's a suggestion. This is just something I put together quickly so lots of room to improve. It relies on the Feature file name being identical to the title of the feature you provide in the description. It also assumes you have a conventional folder structure for your SpecFlow VS project as there is a lot of string manipulation.
Firstly, the calling code should use the SpecFlow BeforeScenario attribute. Something like this:
public void BeforeScenario()
{
//grabs Feature Title from SpecFlow context
var featureName = FeatureContext.Current.FeatureInfo.Title;
//Calls method to obtain path of file
var featureFilePath = GetFeatureFilePath(featureName);
}
The method GetFeatureFilePath will then look like this:
private static string GetFeatureFilePath(string featureName)
{
string startupPath = Environment.CurrentDirectory;
var splitStartupPath = startupPath.Split(new[] {"\\"}, StringSplitOptions.None);
var featureFolder = splitStartupPath[0] + #"\\" +
splitStartupPath[1] + #"\\" +
splitStartupPath[2] + #"\\" +
splitStartupPath[3] + #"\\" +
splitStartupPath[4] + #"\\" +
splitStartupPath[5] + #"\\Features\";
var dir = new DirectoryInfo(featureFolder);
foreach (var fi in dir.GetFiles())
{
if (fi.FullName.Contains(featureName))
return fi.FullName;
}
return "No Feature File Found With Title: " + featureName;
}
It grabs your current directory and splits it to the point where the Features folder should be. It then iterates through each feature file until it finds one that contains your feature title in its path name and returns that as a full path.
I'm not aware of any other way to get this currently.
I don't think knowing the path to the feature file will be possible, as the feature file is used to generate a file containing the unit tests and this is compiled and copied to the test run directory.
The simplest thing will be to set the files as part of the solution and then have them copied to the output directory when the project builds.
If you are using NUnit as the test framework then the files should be in the same directory as the tests are executing so you should just be able to load them without specifying any path, or using the Assembly.GetExecutingAssembly().Location to findout where the code is actually executing.
If you are using MSTest then you need to add a [DeploymentItem(FileToDeploy)] attribute to the test to ensure that the file actually gets deployed with the tests when they are run. Unfortunately as Specflow generates the tests it won't add this for you. To solve this you need to create a partial class which has the same name as the class which contains the tests. This class is called the same as the feature with 'Feature' tagged on the end. So if you have this in your feature:
Feature: Do A Thing
The your test class will be called DoAThingFeature
so you need to create a partial class like this:
[DeploymentItem("FileToDeploy.ext")]
public partial class DoAThingFeature
{}
to ensure that MsTest copies the file you need to the correct directory.
Edit
based on your comment you could maybe do something similar to this
add tags to your feature #hasFiles #source:myFile.xlsx
Then you could add this class:
[Binding]
public class DeployFiles
{
[BeforeScenario("hasFiles")]
public void CopyFiles()
{
..in here find the current executing directory and search
..the subtree for any files defined in the
..ScenarioInfo.Tags array that start with `source:` and copy
..them to the current executing directory
}
}
then any scenario tagged with the #hasFiles will deploy any files specified by #source tags to the root directory where the tests are running.
Not pretty and I'm not certain it'll work, but it might.
maybe this could help you , in .net 4.5 you can get the hold of the path to the caller, take a look at this thread source path in .net 4.5

The 'MapPath' method cease to work as soon as it's procedure is moved to a C# class file

I'm using C# ASP.NET VS2010.
I have a procedure on an .aspx.cs that reads a XML file and works just fine.
It goes like this:
string fileName = "~/App_Data/" + filename + ".xml";
DataSet ds = new DataSet();
ds.ReadXml(MapPath(fileName));
I use this procedure alot to read various files with minimal changes (the file name), therefore, I tried to put the procedure in a Class1.cs file (in the App_Code folder), but I get this error message:
The type or namespace name 'MapPath' does not exist in the namespace 'Microsoft.SqlServer.Server' (are you missing an assembly reference?)
I use this MapPath to read an XML file into a dataset this way:
ds.ReadXml(Server.MapPath(fileName));
The filename is a string variable declared a few lines earlier:
string fileName = "~/App_Data/" + inputString + ".xml";
After putting this line in the class.cs file, the VS2010 asked to resolve the missing Server by replacing it into Microsoft.SqlServer.Server locally (at the same line and not by adding a namespace) , so the line in it's new form looks like this:
ds.ReadXml(Microsoft.SqlServer.Server.MapPath(fileName));
For the record, I made sure that all namespaces on the source .aspx.cs file are at the class file.
Why the difference between the Class1.cs and the .aspx.cs?
How do I workaround this?
What should I change in order to read the XML file from this new class file?
Is there a replacement for my line to read the XML file into the dataset?
MapPath is method of the System.Web.HttpServerUtility class, you need an instance of this class to call the method. In ASP pages, an instance is available in the Server member of the Page; elsewhere, you'll have to supply it. Either as
HttpContext.Current.Server.MapPath(fileName);
which uses the Server variable for the current HttpContext if you're inside one. HttpContext class contains many HTTP-related objects that you're used to access through Page members - like Request, Response, Server. The Current static property gives the context for the request you're currently handling. Inside classes in App_Code folder of your ASP web project, you're safe to assume that there's an active context.
If you wanted to move your class to a separate assembly, it would be better to make the Server (or Context) a parameter of your method and make it the responsibility of the caller to supply one:
public class Class1
{
public void MyMethod(HttpServerUtility server)
{
//...
server.MapPath(fileName);
//...
}
}
From the ASP page it would then be called like class1instance.MyMethod(this.Server);
Microsoft.SqlServer.Server has nothing to do with it, only the class/member names are the same and Visual Studio got it wrong.

How Do I Load A XNA Model From A File Path Instead Of From The Content (eg. Model.FromStream but it doesnt exist)

How do I load an XNA model from a file path instead of from the content (eg. Model.FromStream but it doesnt exist)?? Texture2D has .FromStream, how can I do the equivalent for Model?
You can use Content.Load<Model>("Path here, make sure you use drive letter");
If you really need to have a FromStream method on Model you can use extension methods (Not exactly a perfect duplicate of FromStream but it should work):
public static Model FromPath(this Model model, ContentManager content, string path)
{
return content.Load<Model>(path);
}
EDIT:
I have just tested the above code and apparently, rather than using the drive letter, you need to match the number of "..\\" in areversePathstring with the number of levels of the root directory of theContentManager. The problem is accessing the full directory of theContentManager` which is private (or protected, I'm not sure). Short of using reflection I don't think that that variable can be accessed. If we do know the full Root Directory Path then this should work:
string reversePath = "";
foreach (string level in Content.FullRootDirectory.Split('\\'))// I know there isn't actually a property 'FullRootDirectory' but for the sake of argument,
{
reversePath += "..\\";
}
reversePath = reversePath.Substring(4);
I've googled a few times and haven't been able to find a way to get the root directory of the ContentManager. I might even ask this as a question here on SO in a bit here.
OK here is the final thing (using the answer from the question linked to above):
Add a reference to System.Windows.Forms to your project
Add the following code to the top of your Game1.cs file:
using System.IO;
using TApplication = System.Windows.Forms.Application;
Add this code wherever you need it, like in LoadContent or an extension method:
string ContentFullPath = Path.Combine(Path.GetDirectoryName(TApplication.ExecutablePath),
Content.RootDirectory);
string reversePath = "";
foreach (string level in ContentFullPath.Split('\\'))
{
reversePath += "..\\";
}
reversePath = reversePath.Substring(3);
Model test = Content.Load<Model>(Path.Combine(ContentFullPath, reversePath) + "*Your file name here*");

Using the Web Application version number from an assembly (ASP.NET/C#)

How do I obtain the version number of the calling web application in a referenced assembly?
I've tried using System.Reflection.Assembly.GetCallingAssembly().GetName() but it just gives me the dynamically compiled assembly (returning a version number of 0.0.0.0).
UPDATE: In my case I needed a solution that did not require a reference back to a class within the web application assembly. Jason's answer below (marked as accepted) fulfils this requirement - a lot of others submitted here don't.
Here is some code I use that supports getting the application's "main" assembly from either Web or non-web apps, you can then use GetName().Version to get the version.
It first tries GetEntryAssembly() for non-web apps. This returns null under ASP.NET.
It then looks at HttpContext.Current to determine if this is a web application. It then uses the Type of the current HttpHandler - but this type's assembly might be a generated ASP.NET assembly if the call is made from with an ASPX page, so it traverses the HttpHandler's BaseType chain until it finds a type that isn't in the namespace that ASP.NET uses for its generated types ("ASP").
This will usually be a type in your main assembly (eg. The Page in your code-behind file). We can then use the Assembly of that Type.
If all else fails then fall back to GetExecutingAssembly().
There are still potential problems with this approach but it works in our applications.
private const string AspNetNamespace = "ASP";
private static Assembly getApplicationAssembly()
{
// Try the EntryAssembly, this doesn't work for ASP.NET classic pipeline (untested on integrated)
Assembly ass = Assembly.GetEntryAssembly();
// Look for web application assembly
HttpContext ctx = HttpContext.Current;
if (ctx != null)
ass = getWebApplicationAssembly(ctx);
// Fallback to executing assembly
return ass ?? (Assembly.GetExecutingAssembly());
}
private static Assembly getWebApplicationAssembly(HttpContext context)
{
Guard.AgainstNullArgument(context);
object app = context.ApplicationInstance;
if (app == null) return null;
Type type = app.GetType();
while (type != null && type != typeof(object) && type.Namespace == AspNetNamespace)
type = type.BaseType;
return type.Assembly;
}
UPDATE:
I've rolled this code up into a small project on GitHub and NuGet.
I find that the simplest one-liner way to get the version of your "main" assembly (instead of the dynamic one) is:
typeof(MyMainClass).Assembly.GetName().Version
Use your top-level class, which isn't likely to ever "change its meaning" or to be replaced as part of a refactoring effort, as MyMainClass. You know in which assembly this very class is defined and there can no longer be confusion as to where the version number comes from.
I prefer the Web.Config to store the current version of the site.
You can also try create an AssemblyInfo.cs file in the web application root that has the following:
using System.Reflection;
using System.Runtime.CompilerServices;
...
[assembly: AssemblyVersion("1.0.*")]
...
then access the value via the code like this:
System.Reflection.Assembly.GetExecutingAssembly()
Here is more informaiton on the AssemblyInfo class.
To add to the responders that have already posted. In order to get the assembly version in an ASP.Net web application you need to place a method in the code behind file similar to:
protected string GetApplicationVersion() {
return System.Reflection.Assembly.GetExecutingAssembly().GetName().Version.ToString();
}
In the ASPX page you want to display the version number simply place:
<%= GetApplicationVersion() %>
Just in case anyone is still interested; this should do the trick and should be a tad safer than just taking the BaseType of ApplicationInstance to get your hands on the Global.asax implementation.
Global.asax is always compiled into the same assembly as the assembly attributes from AssemblyInfo.cs, so this should work for all web applications that define a Global.asax.
For those that don't define their own Global.asax, it will fall back to the version of the generated global_asax type, which is always 0.0.0.0, and for applications that aren't web applications, it will just return no version at all.
Bonus; using the BuildManager class does not require an active HttpContext instance, which means you should be able to use this from application startup code as well.
public static Version GetHttpApplicationVersion() {
Type lBase = typeof(HttpApplication);
Type lType = BuildManager.GetGlobalAsaxType();
if (lBase.IsAssignableFrom(lType))
{
while (lType.BaseType != lBase) { lType = lType.BaseType; }
return lType.Assembly.GetName().Version;
}
else
{
return null;
}
}
HttpContext.Current.ApplicationInstance is derived from the class in the global.asax.cs. You can do the following
var instance = HttpContext.Current.ApplicationInstance;
Assembly asm = instance.GetType().BaseType.Assembly;
System.Version asmVersion = asm.GetName().Version;
It works both in ASP.NET (ASPX) and ASP.NET MVC
I encountered a similar problem, and thought you might find the solution useful.
I needed to report the current application version (of a web application project) from a custom server control, where the server control was contained in a different library. The problem was that the "easiest" assembly getters did not provide the right assembly.
Assembly.GetExecutingAssembly() returned the assembly containing the control; not the application assembly.
Assembly.GetCallingAssembly() returned different assemblies depending on where I was at in the call tree; usually System.Web, and sometimes the assembly containing the control.
Assembly.GetEntryAssembly() returned null.
new StackTrace().GetFrames()[idx].GetMethod().DeclaringType.Assembly retrieves the assembly of a frame in the stack trace at index idx; however, besides being inelegant, expensive, and prone to miscalculation on the frame index, it is possible for the stack trace to not contain any calls to the application assembly.
Assembly.GetAssembly(Page.GetType()) scored me the App_Web_##$##$%# assembly containing the dynamically generated page. Of course, the dynamic page inherits a class from my application assembly, so that led to the final solution:
Assembly.GetAssembly(Page.GetType().BaseType)
With the assembly reference in hand, you can drill to the version through its name:
var version = Assembly.GetAssembly(Page.GetType().BaseType)
.GetName()
.Version;
Now, this solution works because I had a reference to a type from the application assembly. We don't use any pages that do not inherit from a code behind, so it happens to be effective for us, but your mileage may vary if your organization's coding practices are different.
Happy coding!
Version version = new Version(Application.ProductVersion);
string message = version.ToString();
Some info here: http://www.velocityreviews.com/forums/showpost.php?p=487050&postcount=8
in asp.net 2.0 each page is built into it own assembly, so only the dll
the AssemblyInfo.cs is built into will
return the correct answer. just add a
static method to AssemblyInfo.cs that
returns the version info, and call
this method from your other pages.
-- bruce (sqlwork.com)
But I wrote a simple method to do that:
public static string GetSystemVersion(HttpServerUtility server)
{
System.Xml.XmlDocument doc = new System.Xml.XmlDocument();
doc.Load(server.MapPath("~/web.config"));
System.Xml.XmlNamespaceManager ns = new System.Xml.XmlNamespaceManager(doc.NameTable);
ns.AddNamespace("bla", "http://schemas.microsoft.com/.NetConfiguration/v2.0");
System.Xml.XmlNode node = doc.SelectSingleNode("/bla:configuration/bla:system.web/bla:authentication/bla:forms[#name]", ns);
string projectName = "";
if (node != null && node.Attributes != null && node.Attributes.GetNamedItem("name") != null)
projectName = node.Attributes.GetNamedItem("name").Value; //in my case, that value is identical to the project name (projetname.dll)
else
return "";
Assembly assembly = Assembly.Load(projectName);
return assembly.GetName().Version.ToString();
}
If you are looking for this from a web control, one hack is to find the type of the code-behind Page (ie. the class that inherits from System.Web.UI.Page). This is normally in the consumer's web assembly.
Type current, last;
current = Page.GetType();
do
{
last = current;
current = current.BaseType;
} while (current != null && current != typeof(System.Web.UI.Page));
return last;
I hope there is a better way.
The question states with no reference (instances) it did not (originally) say with no knowledge of web application types.
EDIT the OP clarified to state that yes they do really require no knowledge of types within the calling web assembly, so the answer is appropriate. However I would seriously consider refactoring such a solution such that the version is passed into the other assembly.
For most people in this scenario if you know the custom HttpApplication type:
typeof(MyHttpApplication).Assembly.GetName().Version
and if you only have a dynamic generated type:
typeof(DynamiclyGeneratedTypeFromWebApp).BaseType.Assembly.GetName().Version
Stop voting me down for this answer :)
So, I had to get the Assembly from a referenced dll.
In the asp.NET MVC/WebAPI world, there is always going to be at least one class which inherits from System.Web.HttpWebApplication. The implementation below searches for that class.
using System;
using System.Linq;
static Assembly GetWebAssembly() => AppDomain.CurrentDomain.GetAssemblies().FirstOrDefault(a => a.GetExportedTypes().Any(t => t.BaseType?.FullName == "System.Web.HttpApplication"));
The above uses System.Linq in order to find that relationship, but this can also be implemented without.
First, we get all loaded assemblies
AppDomain.CurrentDomain.GetAssemblies()
Then, enumerate through the IEnumerable<Assembly>, and get all of the types directly located in the assembly.
a.GetExportedTypes()
Then, see if any of the types inherit from System.Web.HttpWebApplication
t.BaseType?.FullName == "System.Web.HttpApplication"
In my implementation, I ensured this code would only be called once, but if that is not guaranteed, I'd highly wrapping this in a Lazy<T> or other cached lazy load implementation as it is rather expensive to keep performing the blind search.
using System;
using System.Linq;
// original method
private static Assembly GetWebAssembly() => AppDomain.CurrentDomain.GetAssemblies().FirstOrDefault(a => a.GetExportedTypes().Any(t => t.BaseType?.FullName == "System.Web.HttpApplication"));
// lazy load implementation
private static Lazy<Assembly> _webAssembly = new Lazy<Assembly>(GetWebAssembly);
public static Assembly WebAssembly { get => _webAssembly.Value; }

Categories

Resources