Determing the physical path of a user friendly url - asp.net

I have been asked to look at updating an old ASP.net Web Forms application to ASP.net 4.5; specifically to implement Microsoft's 'User Friendly' routing mechanism (NuGet Package Microsoft.AspNet.FriendlyUrls).
Generally, the upgrade was straightforward, but I am left with one problem.
The original developer attached/associated 'meta data' XML files to many of the web pages.. For example, /Products/Tables/Oak.aspx might also have the following meta file /Products/Tables/Oak.aspx.meta
When the page loads, it 'looks' for the meta file and loads it. In a non-rewritten URL environment, this was easy...
string metaUrl = Page.Request.Url.AbsolutePath + ".meta";
string metaPath = Page.Server.MapPath(metaUrl);
If (System.IO.File.Exists(metaPath)) {
LoadMetaFile(metaPath);
}
In a 'Friendly URL' environment, this is not so easy as the original URL might be rewritten to /Products/Tables/Oak or maybe even rewritten completely via a custom MapPageRoute() definition.
Does anyone know if there a way that I can find/determine the 'true' path of the page?

The solution posted by Petriq ASP.NET WebForms: Request.GetFriendlyUrlFileVirtualPath() returns empty string works perfectly in my scenario.
For reference, here is Petriq's code for his HttpRequest extension method:
using System.Web;
using System.Web.Routing;
using Microsoft.AspNet.FriendlyUrls;
namespace Utils.Extensions
{
public static class HttpRequestExtensions
{
public static string GetFileVirtualPathFromFriendlyUrl(this HttpRequest request)
{
string ret = string.Empty;
ret = request.GetFriendlyUrlFileVirtualPath();
if (ret == string.Empty)
{
foreach (RouteBase r in RouteTable.Routes)
{
if (r.GetType() == typeof(Route))
{
Route route = (Route)r;
// Following line modified for case-insensitive comparison
if (String.Compare("/" + route.Url, request.Path, true) == 0)
{
if (route.RouteHandler.GetType() == typeof(PageRouteHandler))
{
PageRouteHandler handler = (PageRouteHandler)route.RouteHandler;
ret = handler.VirtualPath;
}
break;
}
}
}
}
return ret;
}
}
}

Related

Is there a version of IsLocalUrl for WebForms?

I'm writing a mixed app using some MVC and some Webforms screens. I need to invoke a WebForms screen with a ReturnUrl in a hidden field. I'd like to validate the ReturnUrl before transferring back to it. MVC has an Url.IsLocalUrl function, but it doesn't seem to work on WebForm screens, so I use the UrlHelper class. But when I use it I get a NullReferenceException:
UrlHelper url = new UrlHelper();
if (url(validaddr)) <--- get NullReferenceException
{
}
Any ideas?
I use the below extension method to validate local url's in web forms. Hope this helps you too.
public static bool IsLocalURL(this string _url)
{
bool flag = false;
try
{
var url = new Uri(_url);
var ctx = HttpContext.Current;
if (url.Host.Equals(ctx.Request.Url.Host) && url.Port.Equals(ctx.Request.Url.Port))
flag = true;
}
catch { }
return flag;
}
This extension method is for string. You may create a similar for Uri class as well.
I came here trying to solve the same problem. I used RequestExtensions.IsUrlLocalToHost in System.Web.WebPages (available in nuget package Microsoft.AspNet.WebPages v3.2.6)
Doc here: https://learn.microsoft.com/en-us/dotnet/api/system.web.webpages.requestextensions.isurllocaltohost?view=aspnet-webpages-3.2
Assuming you have an HttpRequest to work with (you will probably need this anyway to compare the URL to the underlying host URL), you need to first convert your HttpRequest to HttpRequestBase:
var httpRequestBase = new HttpRequestWrapper(HttpContext.Current.Request) as HttpRequestBase;
Then you can perform:
httpRequestBase.IsUrlLocalToHost(myUrlString)
Code should be:
UrlHelper url = new UrlHelper();
if (url.IsLocalUrl(validaddr)) <--- get NullReferenceException
{
}

#Url.Content doesnt resolve absolute path on one server but does on another

We currently have two different servers on same domain. But one server resolves
#Url.Content("~/api/User")'
as
http://domain.com/virtualdirectory/api/User
where as other server doesnt resolve it absolutely; rather it resolves it relatively like
api/user
The code base is same and we are using MVC4. I am not sure as to where we went wrong or if there is any IIS/DNS settings that need to be done in order to get this fixed.
All help is appreciated; thanks :)
This is related with the IIS Rewriting module in your IIS web server that return the path to http://domain.com/virtualdirectory/api/User
Take a look on the part of source code of #Url.Content below:
private static string GenerateClientUrlInternal(HttpContextBase httpContext, string contentPath)
{
if (String.IsNullOrEmpty(contentPath))
{
return contentPath;
}
// can't call VirtualPathUtility.IsAppRelative since it throws on some inputs
bool isAppRelative = contentPath[0] == '~';
if (isAppRelative)
{
string absoluteContentPath = VirtualPathUtility.ToAbsolute(contentPath, httpContext.Request.ApplicationPath);
return GenerateClientUrlInternal(httpContext, absoluteContentPath);
}
// we only want to manipulate the path if URL rewriting is active for this request, else we risk breaking the generated URL
bool wasRequestRewritten = _urlRewriterHelper.WasRequestRewritten(httpContext);
if (!wasRequestRewritten)
{
return contentPath;
}
// Since the rawUrl represents what the user sees in his browser, it is what we want to use as the base
// of our absolute paths. For example, consider mysite.example.com/foo, which is internally
// rewritten to content.example.com/mysite/foo. When we want to generate a link to ~/bar, we want to
// base it from / instead of /foo, otherwise the user ends up seeing mysite.example.com/foo/bar,
// which is incorrect.
string relativeUrlToDestination = MakeRelative(httpContext.Request.Path, contentPath);
string absoluteUrlToDestination = MakeAbsolute(httpContext.Request.RawUrl, relativeUrlToDestination);
return absoluteUrlToDestination;
}
Use the codes below to check whether your web servers are having the URL rewritten:
bool requestWasRewritten = (httpWorkerRequest != null && httpWorkerRequest.GetServerVariable("IIS_WasUrlRewritten") != null);
And Also:
private volatile bool _urlRewriterIsTurnedOnCalculated = false;
private bool _urlRewriterIsTurnedOnValue;
private object _lockObject = new object();
private bool IsUrlRewriterTurnedOn(HttpContextBase httpContext)
{
// Need to do double-check locking because a single instance of this class is shared in the entire app domain (see PathHelpers)
if (!_urlRewriterIsTurnedOnCalculated)
{
lock (_lockObject)
{
if (!_urlRewriterIsTurnedOnCalculated)
{
HttpWorkerRequest httpWorkerRequest = (HttpWorkerRequest)httpContext.GetService(typeof(HttpWorkerRequest));
//bool urlRewriterIsEnabled = (httpWorkerRequest != null && httpWorkerRequest.GetServerVariable(UrlRewriterEnabledServerVar) != null);
bool urlRewriterIsEnabled = (httpWorkerRequest != null && httpWorkerRequest.GetServerVariable("IIS_UrlRewriteModule") != null);
_urlRewriterIsTurnedOnValue = urlRewriterIsEnabled;
_urlRewriterIsTurnedOnCalculated = true;
}
}
}
return _urlRewriterIsTurnedOnValue;
}
In summary, If both requestWasRewritten and IsUrlRewriterTurnedOn
return true, that means one of your web server has IIS Rewrite Module
turned on and running while the other one doesn't have.
For more details on ASP.NET MVC source codes, please refer to this link:
http://aspnetwebstack.codeplex.com/
Hope it helps!

FB signed_request issue ASP.net c#

I want to access signed_request param on my asp.net page. FB is redirecting the request successfully to the right page. However, it is a GET request thus no signed_request param is passed.
Please help me out.
Trying to read the value using Request.Form["signed_request"] but it is blank.
Please let me know if this is due to wrong configuration or due to some change at FB end. How do I get this working.
Thanks
Syed Adil Umair
As you probably already know, when your page tab app is firstly loaded, Facebook makes a POST request to the URL you specified in application's Page Tab URL (or Secure Page Tab URL) along with the signed_request string. This is why its value is not null on your landing page. But when you click on a link to navigate to another page inside your tab app, it suddenly becomes null. This is how it should be because you are making a GET request with no signed_request. To make your other pages able to access signed_request, you need to store it. I came up with the following code which works for me but if someone comes with a better solution, I would be glad to hear it:
public static string StoredSignedRequest
{
get
{
string signedRequest = HttpContext.Current.Request["signed_request"];
// If signed_request is provided, store it
if (!String.IsNullOrEmpty(signedRequest))
{
WriteCookie("fb-app-signed-request", signedRequest);
return signedRequest;
}
else
{
return ReadCookie("fb-app-signed-request");
}
}
}
public static void WriteCookie(string strCookieName, string strCookieValue)
{
var hcCookie = new HttpCookie(strCookieName, strCookieValue);
HttpContext.Current.Response.Cookies.Set(hcCookie);
}
public static string ReadCookie(string strCookieName)
{
foreach (string strCookie in HttpContext.Current.Response.Cookies.AllKeys)
{
if (strCookie == strCookieName)
{
return HttpContext.Current.Response.Cookies[strCookie].Value;
}
}
foreach (string strCookie in HttpContext.Current.Request.Cookies.AllKeys)
{
if (strCookie == strCookieName)
{
return HttpContext.Current.Request.Cookies[strCookie].Value;
}
}
return null;
}
Then you can use a JSON parser to parse the value of StoredSignedRequest. In my case, I use Newtonsoft:
public static JObject GetSignedRequestJsonObject()
{
string signed_request = StoredSignedRequest;
if (String.IsNullOrEmpty(signed_request))
{
// If signed_request is null on all pages except the landing page, add the following code to all pages so that it is stored:
// <input type="hidden" name="signed_request" value="<%= FacebookAppHelper.StoredSignedRequest %>" />
return null;
}
string payload = signed_request.Split('.')[1];
UTF8Encoding encoding = new UTF8Encoding();
string decodedJson = payload.Replace("=", string.Empty).Replace('-', '+').Replace('_', '/');
byte[] base64JsonArray = Convert.FromBase64String(decodedJson.PadRight(decodedJson.Length + (4 - decodedJson.Length % 4) % 4, '='));
string json = encoding.GetString(base64JsonArray);
return JObject.Parse(json);
}
The critical part is to not forget to add the hidden field (see commented line above) to all pages that you plan to access using a GET request inside your page tab application.

How do I get ASP.NET WebForms Routing to route .asmx JSON calls properly?

I am attempting to implement multi-tenancy in a legacy ASP.NET WebForms app. I want the URL to indicate the proper client, like so:
http://example.com/client_name/Default.aspx
http://example.com/client_name/MyWebService.asmx
However, I cannot get it to route the .asmx's properly. This routing rule picks up all incoming urls just fine:
routes.Add("ClientSelector", new System.Web.Routing.Route
(
"{client}/{*path}",
routeHandler: new ClientRoute()
));
But I am having issues with handling .asmx calls. Here's my IRouteHandler, below. The error I get is:
A first chance exception of type 'System.Web.Services.Protocols.SoapException' occurred in System.Web.Services.dll
Additional information: Unable to handle request without a valid action parameter. Please supply a valid soap action.
It's supposed to be JSON, but for some reason it's not working. I am setting the content-type - if I send this same exact request without routing, it works fine.
public class ClientRoute : System.Web.Routing.IRouteHandler
{
private string m_Path;
private string m_Client;
public ClientRoute() { }
public bool IsReusable { get { return true; } }
public IHttpHandler GetHttpHandler(System.Web.Routing.RequestContext requestContext)
{
this.m_Path = (string)requestContext.RouteData.Values["path"];
this.m_Client = (string)requestContext.RouteData.Values["client"];
string virtualPath = "~/" + this.m_Path;
bool shouldValidate = false;
if (shouldValidate && !UrlAuthorizationModule.CheckUrlAccessForPrincipal(
virtualPath, requestContext.HttpContext.User,
requestContext.HttpContext.Request.HttpMethod))
{
requestContext.HttpContext.Response.StatusCode = (int)HttpStatusCode.Unauthorized;
requestContext.HttpContext.Response.End();
return null;
}
else
{
HttpContext.Current.RewritePath(virtualPath);
HttpContext.Current.Items.Add("Client", this.m_Client);
if (virtualPath.EndsWith(".aspx"))
return (IHttpHandler)BuildManager.CreateInstanceFromVirtualPath(virtualPath, typeof(Page));
else
{
var asmxPos = virtualPath.IndexOf(".asmx", StringComparison.OrdinalIgnoreCase);
if (asmxPos >= 0)
{
// What goes here? This isn't working...
var asmxOnlyVirtualPath = virtualPath.Substring(0, asmxPos + 5);
return new System.Web.Services.Protocols.WebServiceHandlerFactory().GetHandler(
HttpContext.Current, HttpContext.Current.Request.HttpMethod, asmxOnlyVirtualPath, HttpContext.Current.Server.MapPath(asmxOnlyVirtualPath));
}
else
return new StaticRoute();
}
}
}
}
Relevant links:
Getting ScriptHandlerFactory handler
The open source http://www.teamlab.com project is built with ASP.NET Webforms, and uses a multitenant/saas model. I noticed you posted another question inquiring about multitenancy.
Perhaps you can look into their code for reference ideas.
I tried my best, ended up failing, and converted all my web services to WCF .svc services instead.

IIS 7 with URL Rewrite Module 2.0 - setting 401 status codes and the ReturnUrl

I have a website hosted on IIS 7 with the URL Rewrite module 2.0 installed. It is run by a content management system that looks at the URL and returns a 401 error if the current user does not have permission to view the page. This gets picked up by the ASP.NET URL authorization module which then kicks the page over to the loginUrl page as specified in the web.config file (forms authentication).
This works perfectly on my local machine - which is IIS 7 and Windows 7.
If the URL is, say, /612/some-string the user gets directed to the login page at /66/login?ReturnUrl=/612/some-string.
The URL rewriting looks at the first part of the URL for the document ID. The real URL would be this: index.aspx?documentId=612
Unfortunately, when I deployed this to our staging server, the ReturnUrl isn't the rewritten URL, it's the original URL. This causes all sorts of problems.
The staging server is also IIS 7 with the URL Rewrite Module 2.0 installed. It's Windows 2008 server SP2. Both are running ASP.NET 3.5.
My only guess is that the machine.config file orders the default httpModules differently, and the .NET forms authentication module is jumping in before the URL has been rewritten.
I'll review that soon, but in the meantime, what is experience with this problem and can it be solved?
Update
I also tried changing
Response.StatusCode = 401;
to
FormsAuthentication.RedirectToLoginPage();
Which gets me a bit ahead, but still directs the user back to the URL that hasn't been rewritten.
I can also do this instead of setting the 401:
string currentPage = HttpUtility.UrlEncode(Request.RawUrl);
string loginUrl = FormsAuthentication.LoginUrl + "?ReturnUrl=" + currentPage;
Response.Redirect(loginUrl);
But this seems ugly.
In chapter 2 of the book Developing More=Secure Microsoft ASP.NET 2.0 Applications by Dominick Baier there is a ShowPipeline.ashx which shows the complete pipeline ordering on the server using an HttpHandler:
<%# WebHandler Class='ShowPipeline' Language='c#' %>
using System;
using System.Web;
using System.Reflection;
using System.ComponentModel;
// shows which modules have registered for which event
// add a ?asm=true query string parameter to also show the assemblies
public class ShowPipeline : IHttpHandler
{
static bool _showAssemblies = false;
// names of the pipeline events
static string[] _handlerNames = {
"BeginRequest",
"AuthenticateRequest",
"DefaultAuthentication",
"PostAuthenticateRequest",
"AuthorizeRequest",
"PostAuthorizeRequest",
"ResolveRequestCache",
"PostResolveRequestCache",
"AcquireRequestState",
"PostAcquireRequestState",
"PreRequestHandlerExecute",
"PostRequestHandlerExecute",
"ReleaseRequestState",
"UpdateRequestCache",
"PostUpdateRequestCache",
"EndRequest"
};
public void ProcessRequest(HttpContext ctx)
{
if (ctx.Request.QueryString["asm"] == "true")
_showAssemblies = true;
ctx.Response.Write("<hr>");
foreach (string s in _handlerNames)
{
_showHandlers(s);
}
ctx.Response.Write("<hr>");
}
public void _showHandlers(string handlerName)
{
HttpResponse r = HttpContext.Current.Response;
object key = _getPrivateAppField("Event" + handlerName);
EventHandlerList ehl = (EventHandlerList)_getPrivateAppField("_events");
MulticastDelegate md = (MulticastDelegate)ehl[key];
if (null != md)
{
r.Output.WriteLine("<h2>{0}</h2>", handlerName);
foreach (Delegate d in md.GetInvocationList())
{
Type tt = d.Target.GetType();
string asm = "";
if (_showAssemblies)
{
asm = string.Format("<font color='red'>[{0}]</font>", tt.Assembly.GetName());
}
r.Output.WriteLine("{0}{1}.<font color='blue'>{2}</font><br>", asm, tt, d.Method.Name);
}
}
}
object _getPrivateAppField(string fieldName)
{
return _getPrivateField(typeof(HttpApplication), fieldName, HttpContext.Current.ApplicationInstance);
}
object _getPrivateField(Type t, string fieldName, object o)
{
return t.GetField(fieldName, BindingFlags.Instance | BindingFlags.Static | BindingFlags.NonPublic).GetValue(o);
}
object _getPrivateField(string fieldName, object o)
{
return o.GetType().GetField(fieldName, BindingFlags.Instance | BindingFlags.Static | BindingFlags.NonPublic).GetValue(o);
}
public bool IsReusable { get { return true; } }
}

Resources