How can I resolve ASP.NET "~" app paths to the website root without a Control being present? - asp.net-2.0

I want to Resolve "~/whatever" from inside non-Page contexts such as Global.asax (HttpApplication), HttpModule, HttpHandler, etc. but can only find such Resolution methods specific to Controls (and Page).
I think the app should have enough knowledge to be able to map this outside the Page context. No? Or at least it makes sense to me it should be resolvable in other circumstances, wherever the app root is known.
Update: The reason being I'm sticking "~" paths in the web.configuration files, and want to resolve them from the aforementioned non-Control scenarios.
Update 2: I'm trying to resolve them to the website root such as Control.Resolve(..) URL behaviour, not to a file system path.

Here's the answer:
ASP.Net: Using System.Web.UI.Control.ResolveUrl() in a shared/static function
string absoluteUrl = VirtualPathUtility.ToAbsolute("~/SomePage.aspx");

You can do it by accessing the HttpContext.Current object directly:
var resolved = HttpContext.Current.Server.MapPath("~/whatever")
One point to note is that, HttpContext.Current is only going to be non-null in the context of an actual request. It's not available in the Application_Stop event, for example.

In Global.asax add the following:
private static string ServerPath { get; set; }
protected void Application_BeginRequest(Object sender, EventArgs e)
{
ServerPath = BaseSiteUrl;
}
protected static string BaseSiteUrl
{
get
{
var context = HttpContext.Current;
if (context.Request.ApplicationPath != null)
{
var baseUrl = context.Request.Url.Scheme + "://" + context.Request.Url.Authority + context.Request.ApplicationPath.TrimEnd('/') + '/';
return baseUrl;
}
return string.Empty;
}
}

I haven't debugged this sucker but am throwing it our there as a manual solution for lack of finding a Resolve method in the .NET Framework outside of Control.
This did work on a "~/whatever" for me.
/// <summary>
/// Try to resolve a web path to the current website, including the special "~/" app path.
/// This method be used outside the context of a Control (aka Page).
/// </summary>
/// <param name="strWebpath">The path to try to resolve.</param>
/// <param name="strResultUrl">The stringified resolved url (upon success).</param>
/// <returns>true if resolution was successful in which case the out param contains a valid url, otherwise false</returns>
/// <remarks>
/// If a valid URL is given the same will be returned as a successful resolution.
/// </remarks>
///
static public bool TryResolveUrl(string strWebpath, out string strResultUrl) {
Uri uriMade = null;
Uri baseRequestUri = new Uri(HttpContext.Current.Request.Url.GetLeftPart(UriPartial.Authority));
// Resolve "~" to app root;
// and create http://currentRequest.com/webroot/formerlyTildeStuff
if (strWebpath.StartsWith("~")) {
string strWebrootRelativePath = string.Format("{0}{1}",
HttpContext.Current.Request.ApplicationPath,
strWebpath.Substring(1));
if (Uri.TryCreate(baseRequestUri, strWebrootRelativePath, out uriMade)) {
strResultUrl = uriMade.ToString();
return true;
}
}
// or, maybe turn given "/stuff" into http://currentRequest.com/stuff
if (Uri.TryCreate(baseRequestUri, strWebpath, out uriMade)) {
strResultUrl = uriMade.ToString();
return true;
}
// or, maybe leave given valid "http://something.com/whatever" as itself
if (Uri.TryCreate(strWebpath, UriKind.RelativeOrAbsolute, out uriMade)) {
strResultUrl = uriMade.ToString();
return true;
}
// otherwise, fail elegantly by returning given path unaltered.
strResultUrl = strWebpath;
return false;
}

public static string ResolveUrl(string url)
{
if (string.IsNullOrEmpty(url))
{
throw new ArgumentException("url", "url can not be null or empty");
}
if (url[0] != '~')
{
return url;
}
string applicationPath = HttpContext.Current.Request.ApplicationPath;
if (url.Length == 1)
{
return applicationPath;
}
int startIndex = 1;
string str2 = (applicationPath.Length > 1) ? "/" : string.Empty;
if ((url[1] == '/') || (url[1] == '\\'))
{
startIndex = 2;
}
return (applicationPath + str2 + url.Substring(startIndex));
}

Instead of using MapPath, try using System.AppDomain.BaseDirectory. For a website, this should be the root of your website. Then do a System.IO.Path.Combine with whatever you were going to pass to MapPath without the "~".

Related

Can this be done using ASP.NET URL Rewriting

There are about 1700 articles listed on my website created using ASP.NET 4.0 Web Forms. These articles have a url format as:
http://www.mymymyarticles.com/Article.aspx?ID=400
I have explored ASP.NET Friendly URLs as well IIS URL Rewrite. These extensions are great but once a rule is created, they treat all url's generically.
Is it possible that I manually generate my own url string for every url that exists on my website? For eg:
I want to permanent redirect http://www.mymymyarticles.com/Article.aspx?ID=400 to http://www.mymymyarticles.com/this-is-a-very-long-url whereas
http://www.mymymyarticles.com/Article.aspx?ID=500 can be redirected to http://www.mymymyarticles.com/article/short-url and
http://www.mymymyarticles.com/Article.aspx?ID=523 can be redirected to http://www.mymymyarticles.com/very-short-url
So you can see there is no uniformity in the url's that I want to manually generate. Basically I want full control over the url's. How can I go about this. Will it affect performance?
Any examples are appreciated.
Do you have a way of mapping an ID to the url of the new page? If that is the case, you could probably achieve this with ASP.NET Routing. What I would do is start with defining a route:
var route = routes.MapRoute(
"LegacyDocument",
"Articles.aspx{*pathInfo}",
null,
constraints: new { pathInfo = new LegacyDocumentRouteConstraint() }
);
route.RouteHandler = new RedirectRouteHandler();
This route merely captures any requests for /articles.aspx, but it has a constraint and a custom route handler.
The purpose of the constraint is to ensure we at least have the ID query string property and it is a number:
public class LegacyDocumentRouteConstraint : IRouteConstraint
{
public bool Match(HttpContextBase httpContext, Route route, string parameterName, RouteValueDictionary values, RouteDirection routeDirection)
{
if (routeDirection == RouteDirection.UrlGeneration)
{
// Don't both doing anything while we generate links.
return false;
}
string id = httpContext.Request.QueryString["id"];
if (string.IsNullOrWhiteSpace(id))
{
// No query string argument was provided.
return false;
}
int documentId;
if (!int.TryParse(id, out documentId))
{
// The Id is not a number.
return false;
}
// Set the output document Id in the route values.
values["documentId"] = documentId;
return true;
}
}
If the Id was not provided, or was not a number, we can't match to an existing document, so the route will be skipped over. But when the constraint is satisfied, we store a variable in the route values values["documentId"] = documentId so we can then use it again (without having to parse it from the query string again) later in the route handler:
public class RedirectRouteHandler : IRouteHandler
{
public IHttpHandler GetHttpHandler(RequestContext context)
{
int documentId = (int)context.RouteData.Values["documentId"];
return new RedirectLegacyDocumentHttpHandler(documentId);
}
private class RedirectLegacyDocumentHttpHandler : IHttpHandler
{
private int _documentId;
public RedirectHttpHandler(int documentId)
{
_documentId = documentId;
}
public bool IsReusable { get { return false; } }
public void ProcessRequest(HttpContext context)
{
var response = context.Response;
string url = ResolveNewDocumentUrl();
if (url == null)
{
// Issue a 410 to say the document is no longer available?
response.StatusCode = 410;
}
else
{
// Issue a 301 to the new location.
response.RedirectPermanent(url);
}
}
public string ResolveNewDocumentUrl()
{
// Resolve to a new url using _documentId
}
}
}
Route handlers perform the logic of mapping from ASP.NET routing back into the IHttpHandler logic of the ASP.NET runtime. In normal MVC, this would map to the standard MvcHandler which invokes controllers, but in our case, we need only to issue a redirect.
In the route handler, we grab our document Id from the route values, and create a new HTTP handler which performs the actual redirect. You'll need to plumb in the bit which you resolve what the actual new url would be (ResolveNewDocumentUrl), but generally it will resolve the url, if the url comes back as null, we'll issue a HTTP 410 Gone response to say to clients (and more importantly crawlers) that the item is no longer there, or it will issue an HTTP 301 Permanent Redirect with the appropriate location header to the new url.
I had overcome this by creating an xml file on the server with the below schema
<URLMapper>
<Code>1</Code>
<OLDURL>%Oldurl.aspx%</OLDURL>
<NEWURL>default.aspx</NEWURL>
<PermanentRedirect>true</PermanentRedirect>
<Order>1</Order>
<Status>true</Status>
</URLMapper>
Loaded this in Application_Start Event to an application variable (in the form of a datatable).
And in the Begin Request --
void Application_BeginRequest(object sender, EventArgs e)
{
if (Application["URLMapper"] == null) return;
DataTable dtURLs = Application["URLMapper"] as DataTable;
if (dtURLs.Rows.Count == 0) return;
string OrigUrl = HttpContext.Current.Request.Url.ToString().ToLower().Replace("'", "`");
DataRow[] drFound = dtURLs.Select("Status = true and '" + OrigUrl.Trim() + "' like oldurl", "Order",DataViewRowState.CurrentRows);
if (drFound.Length == 0) return;
string OldURL = drFound[0]["OldURL"].ToString().Replace("%","");
Response.RedirectPermanent(OrigUrl.Replace(OldURL, drFound[0]["NewURL"].ToString().Trim()), true);
return;
}

SiteMap.CurrentNode returns null when using query parameter

I have written a custom ASP.NET sitemap provider, it works well but if I add a query parameter to a virtual path SiteMap.CurrentNode returns null - it does not find the page. I've put breakpoints in all my code and never once does it enter my virtual path provider with a query parameter. What am I missing here?
I found an answer to my question and post it here for later use. Seems the sitemap provider always uses the path without the querystring parameters when lookup up matching paths. The trick is to not use Reqest.RawUrl in your overriden SiteMapProvider.CurrentNode() function but rather use Request.Path ; I've posted my solution below:
public class CustomSiteMapProvider : SiteMapProvider {
// Implement the CurrentNode property.
public override SiteMapNode CurrentNode {
get {
var currentUrl = FindCurrentUrl();
// Find the SiteMapNode that represents the current page.
var currentNode = FindSiteMapNode(currentUrl);
return currentNode;
}
}
// Get the URL of the currently displayed page.
string FindCurrentUrl() {
try {
// The current HttpContext.
var currentContext = HttpContext.Current;
if (currentContext != null) return currentContext.Request.Path;
throw new Exception("HttpContext.Current is Invalid");
} catch (Exception e) {
throw new NotSupportedException("This provider requires a valid context.", e);
}
}
...

ASP.NET Optimization Framework (JS and CSS minification and Bundling) using CDN and HashContent (Cache Buster or Finger Print)

!!! CAUTION!!!
The accepted answer is good, but if you have a high traffic website there's a chance of attaching v= multiple times. The code contains a checker.
I've been looking for any examples or references where ASP.NET Optimization Framework is used with UseCDN = true and HashContent Number is attached to the Bundles' URI. Unfortunately without any luck. The following is a simplified example of my code.
My Bundling code is pretty Simple
bundles.UseCdn = true;
BundleTable.EnableOptimizations = true;
var stylesCdnPath = "http://myCDN.com/style.css";
bundles.Add(new StyleBundle("~/bundles/styles/style.css", stylesCdnPath).Include(
"~/css/style.css"));
I call the Render from a Master page
<%: System.Web.Optimization.Styles.Render("~/bundles/styles/style.css")%>
The generated code is
<link href="http://myCDN.com/style.css" rel="stylesheet"/>
If I disable UseCDN
/bundles/styles/style.css?v=geCEcmf_QJDXOCkNczldjY2sxsEkzeVfPt_cGlSh4dg1
How can I make the bunlding add v= Hash Content when useCDN is set to true ?
Edit:
I tried using
<%: System.Web.Optimization.BundleTable.Bundles.ResolveBundleUrl("~/bundles/styles/style.css",true)%>
it still will not generate v = hash if CdnUse = true
You can't, Setting UseCdn to true means ASP.NET will serve bundles as is from your CDN Path, no Bundling and Minification will be performed.
The query string v has a value token that is a unique identifier used
for caching. As long as the bundle doesn't change, the ASP.NET
application will request the bundle using this token. If any file in
the bundle changes, the ASP.NET optimization framework will generate a
new token, guaranteeing that browser requests for the bundle will get
the latest bundle.
Have a look at BundleCollection.ResolveBundleUrl Implementation:
// System.Web.Optimization.BundleCollection
/// <summary>Returns the bundle URL for the specified virtual path, including a content hash if requested.</summary>
/// <returns>The bundle URL or null if the bundle cannot be found.</returns>
/// <param name="bundleVirtualPath">The virtual path of the bundle.</param>
/// <param name="includeContentHash">true to include a hash code for the content; otherwise, false. The default is true.</param>
public string ResolveBundleUrl(string bundleVirtualPath, bool includeContentHash)
{
Exception ex = ExceptionUtil.ValidateVirtualPath(bundleVirtualPath, "bundleVirtualPath");
if (ex != null)
{
throw ex;
}
Bundle bundleFor = this.GetBundleFor(bundleVirtualPath);
if (bundleFor == null)
{
return null;
}
if (this.UseCdn && !string.IsNullOrEmpty(bundleFor.CdnPath))
{
return bundleFor.CdnPath;
}
return bundleFor.GetBundleUrl(new BundleContext(this.Context, this, bundleVirtualPath), includeContentHash);
}
Ref: http://www.asp.net/mvc/tutorials/mvc-4/bundling-and-minification
Update
You can have the V hash manually added to your CDN by implementing your own bundle and generate the hash on ApplyTransforms call:
public class myStyleBundle: StyleBundle
{
public myStyleBundle(string virtualPath)
:base(virtualPath)
{
}
public myStyleBundle(string virtualPath, string cdnPath)
: base(virtualPath,cdnPath)
{
MyCdnPath = cdnPath;
}
public string MyCdnPath
{
get;
set;
}
public override BundleResponse ApplyTransforms(BundleContext context, string bundleContent, System.Collections.Generic.IEnumerable<BundleFile> bundleFiles)
{
var response = base.ApplyTransforms(context, bundleContent, bundleFiles);
base.CdnPath = string.Format("{0}?v={1}", this.MyCdnPath, this.HashContent(response));
return response;
}
private string HashContent(BundleResponse response)
{
string result;
using (SHA256 sHA = new SHA256Managed())
{
byte[] input2 = sHA.ComputeHash(Encoding.Unicode.GetBytes(response.Content));
result = HttpServerUtility.UrlTokenEncode(input2);
}
return result;
}
}
Then, simply do:
bundles.Add(new myStyleBundle("~/bundles/styles/style.css", stylesCdnPath).Include(
"~/css/style.css"));
Please note that System.Web.Optimization.BundleResponse creates a hash algorithm based on environment settings:
// System.Web.Optimization.BundleResponse
private static SHA256 CreateHashAlgorithm()
{
if (BundleResponse.AllowOnlyFipsAlgorithms)
{
return new SHA256CryptoServiceProvider();
}
return new SHA256Managed();
}

#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!

Multiple instances use a co-located caching but fail to access, good named caching implementation required

We have been transferring our services and MVC4 website to the cloud, overall this process went fine.
Except for caching, since we have moved to Azure it would also be wise to use some kind of caching which azure provides. We choose for co-located / dedicated caching role which has the advantage that the cache is used over all the instances.
Setting up the caching worked fine, I've got a named caching client which I only initialize when its required. It is set up in a inherited layer of the controllers. As soon as one of the functions is called, it checks if the connection to the data-cache is still there or its created. This all seems to work fine, but I'm building a module do retrieve prices. And multiple ajax inserts (views which get inserted into the page with use of javascript) use these functions, some of them are called at the same time, by multiple ajax views. Some of these views then return either a 404 or 500 error, and I cant explain where these are coming from except a non working caching, or something alike.
Can someone help me with a good implementation of the named caching (co-located or dedicated), since all I can find is many examples illustrating the initializing of the DataCacheFactory, but not of the data insertion and retrieval.
Below is the code as I have it now, I've tried more ways with use of locking etc but this one so far worked best.
private static object magicStick = new object();
private static DataCacheFactory dcf = null;
private static DataCache priceCache = null;
protected void CreateCacheFactory()
{
dcf = new DataCacheFactory();
}
protected void CreatePricesCache()
{
if (dcf == null)
{
CreateCacheFactory();
}
priceCache = dcf.GetCache("Prices");
}
protected PriceData GetPrices(int productID)
{
if (priceCache == null)
{
CreatePricesCache();
}
string cacheKey = "something";
lock (magicStick)
{
PriceData datas = priceCache.Get(cacheKey) as PriceData;
if (datas == null)
{
lock (magicStick)
{
Services svc = new Services();
PriceData pData = svc.PriceService.GetPrices(productID);
if (pData != null && pData.Offers != null && pData.Offers.Count() > 0)
{
datas = pData;
datas.Offers = datas.Offers.OrderBy(pr => (pr.BasePrice + pr.ShippingCosts)).ToArray();
priceCache.Add(cacheKey, datas, new TimeSpan(0, cachingTimePricesKK, 0));
}
}
}
return datas;
}
}
As soon as I get to a page where there are pricelists and the function above is called multiple times with the same arguments, there is a 5-10% chance that it returns an error rather then returning the results. Can anybody help me, im totally stuck with this for a week now and its eating me up inside.
First I'd move your cache and cacheFactory instantiation out of your getPrices method. Also, evaluate your need for the lock - this may be causing timeouts. Another VERY important observation - you are using a constant cache key and saving/retrieving data for every productId with the same cache key. You should be using a cache key like: var cacheKey = string.format("priceDatabyProductId-{0}", productId);. You need to set some breakpoints and examine exactly what you are caching and retrieving from the cache. The code as written will save the first productId to the cache and then keep returning that data regardless of the productId.
Here is a full working example we use in production using the "default" named cache in dedicated cache roles:
public static class MyCache
{
private static DataCacheFactory _cacheFactory = null;
private static DataCache ACache
{
get
{
if (_cacheFactory == null)
{
try
{
_retryPolicy.ExecuteAction(() => { _cacheFactory = new DataCacheFactory(); });
return _cacheFactory == null ? null : _cacheFactory.GetDefaultCache();
}
catch (Exception ex)
{
ErrorSignal.FromCurrentContext().Raise(ex);
return null;
}
}
return _cacheFactory.GetDefaultCache();
}
}
public static void FlushCache()
{
ACache.Clear();
}
// Define your retry strategy: retry 3 times, 1 second apart.
private static readonly FixedInterval _retryStrategy = new FixedInterval(3, TimeSpan.FromSeconds(1));
// Define your retry policy using the retry strategy and the Windows Azure storage
// transient fault detection strategy.
private static RetryPolicy _retryPolicy = new RetryPolicy<StorageTransientErrorDetectionStrategy>(_retryStrategy);
// Private constructor to prevent instantiation
// and force consumers to use the Instance property
static MyCache()
{ }
/// <summary>
/// Add an item to the cache with a key and set a absolute expiration on it
/// </summary>
public static void Add(string key, object value, int minutes = 90)
{
try
{
_retryPolicy.ExecuteAction(() => { ACache.Put(key, value, TimeSpan.FromMinutes(minutes)); });
}
catch (Exception ex)
{
ErrorSignal.FromCurrentContext().Raise(ex);
}
}
/// <summary>
/// Add the object with the specified key to the cache if it does not exist, or replace the object if it does exist and set a absolute expiration on it
/// only valid for Azure caching
/// </summary>
public static void Put(string key, object value, int minutes = 90)
{
try
{
_retryPolicy.ExecuteAction(() => { ACache.Put(key, value, TimeSpan.FromMinutes(minutes)); });
}
catch (Exception ex)
{
ErrorSignal.FromCurrentContext().Raise(ex);
}
}
/// <summary>
/// Get a strongly typed item out of cache
/// </summary>
public static T Get<T>(string key) where T : class
{
try
{
object value = null;
_retryPolicy.ExecuteAction(() => { value = ACache.Get(key); });
if (value != null) return (T) value;
return null;
}
catch (DataCacheException ex)
{
ErrorSignal.FromCurrentContext().Raise(ex);
return null;
}
catch (Exception ex)
{
ErrorSignal.FromCurrentContext().Raise(ex);
return null;
}
}
/// <summary>
/// Microsoft's suggested method for cleaning up resources such as this in a static class
/// to ensure connections and other consumed resources are returned to the resource pool
/// as quickly as possible.
/// </summary>
public static void Uninitialize()
{
if (_cacheFactory == null) return;
_cacheFactory.Dispose();
_cacheFactory = null;
}
}
Note: this is also using the Transient Fault Handling block from the Enterprise Library for transient exception fault handling.

Resources