Better way to get Page Name - asp.net

I'm using this to get the current page name..so this returns for example MyPage.aspx
public string GetCurrentPageName()
{
string urlPath = Request.Url.AbsolutePath;
FileInfo fileInfo = new FileInfo(urlPath);
string pageName = fileInfo.Name;
return pageName;
}
There has to be an easier way? Meaning there's got to be an existing method or property in the .NET framework one would think.

The way I interpret the question, what you're looking for is an efficient way of retrieving the name of the currently executing aspx page, i.e. System.Web.UI.Page.
If that is true you shouldn't have to deal with any FileInfo objects or hit the filesystem. Simply use the AppRelativeVirtualPath property on the page object:
using System;
using System.IO;
using System.Web.UI;
namespace WebApplication1
{
public partial class _Default : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{
string pageName = Path.GetFileNameWithoutExtension(Page.AppRelativeVirtualPath);
}
}
}
If you want to get the fully-qualified (or "rooted") path of your currently executing page you can use Server.MapPath like this:
string path = Server.MapPath(Page.AppRelativeVirtualPath);
Using AppRelativeVirtualPath has the benefit of working even when you're using URL rewriting and, since it doesn't use Request.Url (which is provided by your users), you don't have to worry about potentially malicious data.

Would HttpContext.Current.CurrentHandler be what you're looking for?
Since you're more interested in the physical file name vs the page object something along the lines of
var page = (Page) HttpContext.Current.CurrentHandler;
string url = page.AppRelativeVirtualPath;
This along with the information from #Markus Olsson can give you access to the page in any point during its execution even if you're outside of page class.

I use Request.Url.Segments.Last() , which I think is elegant enough.

just for interest I did little search with intellisence. did not found any property. still same logic in other way round.
string currentPageName = System.IO.Path.GetFileName(Request.Url.AbsolutePath);

As was pointed out in one of the answers to this earlier question of yours, I'd go for an option that didn't need me to create a FileInfo object.
There isn't always a direct mapping between a requested page and a file system object, especially when routing/url rewriting etc comes in to play.

Dim MyPage As String = Path.GetFileName(Page.AppRelativeVirtualPath.ToString).ToString
This one works for me

Not much better, but you could try this extension method:
public static string GetPageName(this Page myPage)
{
FileInfo fi =new FileInfo(myPage.MapPath(myPage.AppRelativeVirtualPath));
return fi.Name;
}
and just call it in your page's "OnInit" or whatever method as:
string pageName = this.GetPageName();
Marc

System.IO.Path.GetFileName(Request.PhysicalPath)

Related

Access .aspx page properties/variable from separate .cs file

Maybe a stupid question. C# 6.0 allows for string replacement using this syntax: $"string content {foo} {bar}". I would like to imitate this behavior in a class I've written for strings passed to it by default. The problem is that I am not sure how to access the public properties/variables. I am not sure if there is a way to access the properties using reflection or by passing this or this.Page to the constructor.
Figured it out:
public static object GetPropValue(object src, string propName)
{
return src.GetType().GetProperty(propName).GetValue(src, null);
}
object UID = GetPropValue(System.Web.HttpContext.Current.Handler, "UID");

ASP.Net WebForms Routing Single Route for Multiple Destinations

I am looking into setting database routing up for a new website I plan to create. I have been looking at the following tutorial with regards to utilising friendlyUrls from a database:
http://www.asp.net/web-forms/tutorials/aspnet-45/getting-started-with-aspnet-45-web-forms/url-routing
However, I would like to use the same route structure for multiple entities. Meaning:
mysite.com/{PlayerName} goes to player.aspx
mysite.com/{TeamName} goes to team.aspx
… and so on …
Could somebody point in the right direction of achieving this with asp.net. Is it possible using the built in routing engine, or should I be looking to code my own HTTPModule for this?
Thanks
David
I'm not sure why so many people say that this cannot be done with routing - maybe I'm not getting something, but the same logic that apparently makes the accepted answer a valid option should be perfectly applicable to a custom route handler, e.g. IRouteHandler or something derived from System.Web.Routing.RouteBase.
You can add "managers" to your RouteCollection (RouteTable.Routes) in the manner of:
routes.Add("MyRoutName", new MyCustomRouteBaseThing())
... Or:
routes.Add(new Route("whatever/{possiblySomething}", new RouteValueDictionary {
{"whatever", null}
}, new MyImplementationOfIRouteHandler()));
... Etcetera, depending on your needs.
If you go with the RouteBase alternative for example, override GetRouteData(), GetVirtualPath() and whatnot. I'm not saying it's necessarily a better option than the accepted answer, I just don't see why routing should be deemed not viable. (What am I missing?)
EDIT: At the time I wrote the above, the "accepted answer" was the one about URL rewriting posted by Tasos K, to whom the bounty was also rewarded. The accepted answer has since been reassigned.
Write two constraints which return boolean whether segment is a team or not / a player or not.
public class IsTeamConstraint : IRouteConstraint
{
public bool Match
(
HttpContextBase httpContext,
Route route,
string parameterName,
RouteValueDictionary values,
RouteDirection routeDirection
)
{
return SomeService.IsTeam(values["teamName"]);
}
}
public class IsPlayerConstraint : IRouteConstraint
{
public bool Match
(
HttpContextBase httpContext,
Route route,
string parameterName,
RouteValueDictionary values,
RouteDirection routeDirection
)
{
return SomeService.IsPlayer(values["playerName"]);
}
}
Set constraint in page route.
void RegisterCustomRoutes(RouteCollection routes)
{
routes.MapPageRoute(
"Team",
"{teamName}",
"~/Team.aspx",
false,
null,
new RouteValueDictionary { { "isTeam", new IsTeamConstraint() } }
);
routes.MapPageRoute(
"Player",
"{playerName}",
"~/Player.aspx",
false,
null,
new RouteValueDictionary { { "isPlayer", new IsPlayerConstraint() } }
);
}
Now when a page is requested registered page routes will use constraint to check that the route is valid and execute page if it is.
I haven't tried this in ASP.Net Forms but I've applications running with constraints developed in ASP.Net MVC. Both type of application (Forms and MVC) shared common routing logic.
I also don't know how this can be done using routing. But one way to achieve this is using URL rewriting instead. The whole process has a few steps and it is rather simple to make.
Applying the URL rewriting
You add at the Global.asax the following function.
void Application_BeginRequest(object sender, EventArgs e)
{
//Here you will get exception 'Index was outside the bounds of the array' when loading home page, handle accordingly
string currentsegment = Request.Url.Segments[1];
string RewritePath = "";
if (IsTeam(currentsegment))
{
RewritePath = "~/team.aspx?team=" + currentsegment;
}
if (IsPlayer(currentsegment))
{
RewritePath = "~/player.aspx?player=" + currentsegment;
}
if (RewritePath != "") {
// Adding all query string items to the new URL
for (int I = 0; I <= Request.QueryString.Count - 1; I++)
{
RewritePath = RewritePath + "&" + Request.QueryString.Keys[I] + "=" + Request.QueryString[I];
}
Context.RewritePath(RewritePath);
}
}
So if the URL has is /some-title-here you can get the some-title-here part using the Request.Url.Segments array.
Then based on that your code detects if this title is a team or a player. In any case you change internally the URL by calling the Context.RewritePath(...).
One important thing is that you need to add all the query string items manually in order to pass them to your pages.
Also, inside your code the Request.Url will know the rewritten URL, not the original.
A quick way to test it is to implement the IsTeam(...) and IsPlayer(...) function as below. With only this code when hitting /player-tasos the ~/player.aspx?player=player-tasos page loads and when hitting /team-stackoverflow the ~/team.aspx?team=team-stackoverflow page loads.
private bool IsTeam(string segment)
{
return segment.StartsWith("team");
}
private bool IsPlayer(string segment)
{
return segment.StartsWith("player");
}
So far this approach works but it has one main issue. When there is a PostBack the URL changes to the one you have set in the Context.RewritePath(...)
Avoiding PostBack issue
To avoid this issue you need to add to your projects two ASP.NET folders
App_Browsers
App_Code
In the App_Code folder you create a file FormRewriter.cs and add the following code (In my demo the root namespace is WebFormsRewriting)
using Microsoft.VisualBasic;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Data;
using System.Diagnostics;
using System.Web;
using System.Web.UI;
namespace WebFormsRewriting
{
public class FormRewriterControlAdapter : System.Web.UI.Adapters.ControlAdapter
{
protected override void Render(System.Web.UI.HtmlTextWriter writer)
{
base.Render(new RewriteFormHtmlTextWriter(writer));
}
}
public class RewriteFormHtmlTextWriter : System.Web.UI.HtmlTextWriter
{
public RewriteFormHtmlTextWriter(HtmlTextWriter writer)
: base(writer)
{
this.InnerWriter = writer.InnerWriter;
}
public RewriteFormHtmlTextWriter(System.IO.TextWriter writer)
: base(writer)
{
base.InnerWriter = writer;
}
public override void WriteAttribute(string name, string value, bool fEncode)
{
// If the attribute we are writing is the "action" attribute, and we are not on a sub-control,
// then replace the value to write with the raw URL of the request - which ensures that we'll
// preserve the PathInfo value on postback scenarios
if ((name == "action"))
{
HttpContext Context = default(HttpContext);
Context = HttpContext.Current;
if (Context.Items["ActionAlreadyWritten"] == null)
{
// Because we are using the UrlRewriting.net HttpModule, we will use the
// Request.RawUrl property within ASP.NET to retrieve the origional URL
// before it was re-written. You'll want to change the line of code below
// if you use a different URL rewriting implementation.
value = Context.Request.RawUrl;
// Indicate that we've already rewritten the <form>'s action attribute to prevent
// us from rewriting a sub-control under the <form> control
Context.Items["ActionAlreadyWritten"] = true;
}
}
base.WriteAttribute(name, value, fEncode);
}
}
}
In the App_Browsers folder you create a file Form.browser and add the following snippet. Note here to put the class name of the Adapter with its namespace.
<browsers>
<browser refID="Default">
<controlAdapters>
<adapter controlType="System.Web.UI.HtmlControls.HtmlForm"
adapterType="WebFormsRewriting.FormRewriterControlAdapter" />
</controlAdapters>
</browser>
</browsers>
And that's it. Adding those two files will handle the PostBack issue. If you put the FormRewriter.cs outside the App_Code folder it will not work. Also those two folders must be uploaded to the production server.
I have used this approach for years in .NET 3.5 and .NET 4.0 without any problems. Today I also tested it in a .NET 4.5 Web Forms project and it works with no issues.
All of the above are based on a ScottGu's article about the subject
As others have pointed out... it would be much better NOT to use this route for both Players and Teams.
It would be preferable to setup two routes...
mysite.com/player/{PlayerName}
mysite.com/team/{TeamName}
In this way you can drive all "player" traffic to Player.aspx, and "team" traffic to Team.aspx, nice and easy.
However... If you really have to support a single route, I recommend that you add it as a third option, and use a 301 Redirect to one of the two above routes.
mysite.com/{PlayerOrTeamName} -> Route.aspx
Let Route.aspx handle requests that don't map to physical files.
Then your Route.aspx code needs to function as a 404 Error handler, but with a catch.. It will check the Players data and the Teams data for an exact match. If it finds one it should do a 301 permanent redirect to the correct /player/ or /team/ route.
Using...
string strCorrectURL = RouteTable.Routes.GetVirtualPath(null, "player", new RouteValueDictionary { { "Name", strValue }});
Response.StatusCode = 301;
Response.Status = "301 Moved Permanently";
Response.AddHeader("Location", strCorrectURL);
Response.End();
This will give you the functionality of a single path, but tell search engines to index the more precise path.
You could skip the RouteTable altogether and just put this code into your default 404 handler.

How do QueryString parameters get bound to Action method parameters?

I have a webforms project, and am attempting to run some code that allows me to make a call to an MVC route and then render the result within the body of the web forms page.
There are a couple of HttpResponse/Request/Context wrappers which I use to execute a call to an MVC route, e.g.:
private static string RenderInternal(string path)
{
var responseWriter = new StringWriter();
var mvcResponse = new MvcPlayerHttpResponseWrapper(responseWriter, PageRenderer.CurrentPageId);
var mvcRequest = new MvcPlayerHttpRequestWrapper(Request, path);
var mvcContext = new MvcPlayerHttpContextWrapper(Context, mvcResponse, mvcRequest);
lock (HttpContext.Current)
{
new MvcHttpHandlerWrapper().PublicProcessRequest(mvcContext);
}
...
The code works fine for executing simple MVC routes, for e.g. "/Home/Index". But I can't specify any query string parameters (e.g. "/Home/Index?foo=bar") as they simply get ignored. I have tried to set the QueryString directly within the RequestWrapper instance, like so:
public class MvcPlayerHttpRequestWrapper : HttpRequestWrapper
{
private readonly string _path;
private readonly NameValueCollection query = new NameValueCollection();
public MvcPlayerHttpRequestWrapper(HttpRequest httpRequest, string path)
: base(httpRequest)
{
var parts = path.Split('?');
if (parts.Length > 1)
{
query = ExtractQueryString(parts[1]);
}
_path = parts[0];
}
public override string Path
{
get
{
return _path;
}
}
public override NameValueCollection QueryString
{
get
{
return query;
}
}
...
When debugging I can see the correct values are in the "request.QueryString", but the values never get bound to the method parameter.
Does anyone know how QueryString values are used and bound from an http request to an MVC controller action?
It seems like the handling of the QueryString value is more complex than I anticipated. I have a limited knowledge of the internals of the MVC Request pipeline.
I have been trying to research the internals myself and will continue to do so. If I find anything I will update this post appropriately.
I have also created a very simple web forms project containing only the code needed to produce this problem and have shared it via dropbox: https://www.dropbox.com/s/vi6erzw24813zq1/StackMvcGetQuestion.zip
The project simply contains one Default.aspx page, a Controller, and the MvcWrapper class used to render out the result of an MVC path. If you look at the Default.aspx.cs you will see a route path containing a querystring parameter is passed in, but it never binds against the parameter on the action.
As a quick reference, here are some extracts from that web project.
The controller:
public class HomeController : Controller
{
public ActionResult Index(string foo)
{
return Content(string.Format("<p>foo = {0}</p>", foo));
}
}
The Default.aspx page:
protected void Page_Load(object sender, EventArgs e)
{
string path = "/Home/Index?foo=baz";
divMvcOutput.InnerHtml = MvcWrapper.MvcPlayerFunctions.Render(path);
}
I have been struggling with this for quite a while now, so would appreciate any advice in any form. :)
MVC framework will try to fill the values of the parameters of the action method from the query string (and other available data such as posted form fields, etc.), that part you got right. The part you missed is that it does so by matching the name of the parameter with the value names passed in. So if you have a method MyMethod in Controller MyController with the signature:
public ActionResult MyMethod(string Path)
{
//Some code goes here
}
The query string (or one of the other sources of variables) must contain a variable named "Path" for the framework to be able to detect it. The query string should be /MyController/MyMethod?Path=Baz
Ok. This was a long debugging session :) and this will be a long response, so bear with me :)
First how MVC works. When you call an action method with input parameters, the framework will call a class called "DefaultModelBinder" that will try and provide a value for each basic type (int, long, etc.) and instance of complex types (objects). This model binder will depend on something called the ValueProvider collection to look for variable names in query string, submitted forms, etc. One of the ValueProviders that interests us the most is the QueryStringValueProvider. As you can guess, it gets the variables defined in the query string. Deep inside the framework, this class calls HttpContext.Current to retrieve the values of the query string instead of relying on the ones being passed to it. In your setup this is causing it to see the original request with localhost:xxxx/Default.aspx as the underlying request causing it to see an empty query string. In fact inside the Action method (Bar in your case) you can get the value this.QueryString["variable"] and it will have the right value.
I modified the Player.cs file to use a web client to make a call to an MVC application running in a separate copy of VS and it worked perfectly. So I suggest you run your mvc application separately and call into it and it should work fine.

How to create an ASP.NET route that redirects to a path that uses part of the route?

I want to create a route that redirects all requests matching certain pattern to a location built using parts of the pattern. I want to grab some segment in the URL and treat the rest like a path to an aspx page in Web Forms application. For example
RouteTable.Routes.MapPageRoute("SomeRouteName", "{something}/{*path}", "~/pages/{*path}/Default.aspx");
Where *path could be something contain "\". The query string should be preserved as a query string.
Is it possible to create souch a route?
I don't know of any way to do it that way.
The more standard way would be to set the target as "~/pages/default.aspx" and then have that page check for the {path} argument and display the corresponding data.
If you really want it in another path, then don't use a {} placeholder. Simply hard code that section of the path (both the source and target).
After looking at several ways to do this I ended up creating my own routing handler that is something like this:
public class SomethingRoutingHandler : IRouteHandler
{
public IHttpHandler GetHttpHandler(RequestContext requestContext)
{
string something = (string)requestContext.RouteData.Values["something"];
string path = (string)requestContext.RouteData.Values["path"];
string virtualPath = "~/" + path + "Default.aspx";
return BuildManager.CreateInstanceFromVirtualPath(virtualPath, typeof(Page)) as Page;
}
}
I then use the RouteData in the pages to access something. I found these articles helpful:
http://msdn.microsoft.com/en-us/magazine/dd347546.aspx
http://www.xdevsoftware.com/blog/post/Default-Route-in-ASPNET-4-URL-Routing.aspx

Writing JavaScript from a Custom Control

I'm new to writing custom controls. I have MyCustomControl.cs and in my Render method I want to render out about 50 lines of JavaScript. What's the best way to do this, use the writer?
protected override void Render(HtmlTextWriter writer)
{
writer.write(#"<script type....rest of opening tag here");
writer.Write(#"
function decode(s)
{
return s.replace(/&/g, ""&"")
.replace(/"/g, '""')
.replace(/'/g, ""'"")
.replace(/</g, ""<"")
.replace(/>/g, "">"");
};"
);
I plan on having around 6 more writer.Write to write out some more sections here. Is that the best approach to actually perform the writing of JavaScript in this manor?
or should I use ClientScript.RegisterClientScriptBlock? So what's the best practice or common way people are writing javascript from a custom control? (I'm not talking about a user control here!!, custom control/Class!)
I also want to keep any indentation for readability once it's spit out/rendered on the client when viewing source.
The answer I'm providing is just taking regular postbacks into account. All the below can be applied using the ScriptManager and its respective methods to do the same.
There's a couple of ways to do it. You can load a web resource and reference it
// The Scripts namespace in this case would actually be a physical folder in your YourNamespace.CustomControlsNamespace
// namespace.
// Also the /Scripts/YourJavaScriptFile.js needs to have it's Build Action property set to Embedded Resource
[assembly: WebResource("YourNamespace.CustomControlsNamespace.Scripts.YourJavaScriptFile.js", "text/javascript")]
namespace YourNamespace.CustomControlsNamespace
{
public CustomControl()
{
...
}
...
protected override OnPreRender(EventArgs e)
{
...
Type type = typeof(CustomControl);
string scriptUrl = Page.ClientScript.GetWebResourceUrl(type, "Acuity.Web.UI.WebControls.Scripts.accdaterange.js");
string key = "yourKey";
if (!Page.ClientScript.IsClientScriptIncludeRegistered(type, key))
{
control.Page.ClientScript.RegisterClientScriptInclude(type, key, scriptUrl);
}
...
}
...
}
You could also reference an external script in your custom control.
namespace YourNamespace.CustomControlsNamespace
{
public CustomControl()
{
...
}
...
protected override OnPreRender(EventArgs e)
{
...
Type type = typeof(CustomControl);
string scriptUrl = Page.ResolveClientUrl("~/yourScriptsFolder/yourExternalScript.js");
string key = "yourKey";
if (!Page.ClientScript.IsClientScriptIncludeRegistered(type, key))
{
control.Page.ClientScript.RegisterClientScriptInclude(type, key, scriptUrl);
}
...
}
...
}
Depends how you want to package it. The advantage of the embedded resource is that you're guaranteed that this script is always with the assembly that your custom control is in. You will most likely have to add some inline JavaScript to wireup your custom control. Try and do this as little as possible. You can do this using Page.ClientScript.RegisterClientScriptBlock(...). You also want to avoid hard-coding what the auto-generated client Ids are in your scripts. They should be passed in as parameters to client-side objects.
Also, once all looks good, you should compress/minify you external JavaScript files. I use YuiCompressor from Yahoo! but there are several others out there.
You should also invest some time into looking at using a JavaScript framework such as jQuery to do a lot of the grunt work. That's it for now. I might add some more later, but these are my words of wisdom for now.

Resources