ASP.NET MVC Routing for files with muliple sub-directories - asp.net

I need to setup a file handler to route with multiple sub directories something like tihs;
http://localhost/images/7/99/786936215595.jpg
I tried putting this in the global.asax file;
routes.Add(
"ImageRoute",
new Route("covers/{filepath}/{filename}",
new ImageRouteHandler()));
I am using the ImageHandler found in this Question, which works great if you have a single sub-directory (ie '/images/15/786936215595.jpg') but fails when you have multiple directories.
I tried setting up a wildcard and that didnt work (ie 'new Route("covers/{filepath}/*/{filename}"')
This is serving images from a large NAS (think something like 3 million images) so its not like I can just move files around.
Thanks!

Ok after much playing around and google fu I found how to make it work.
Change the route definition like this;
routes.Add(
"ImageRoute",
new Route("images/{*filepath}",
new ImageRouteHandler()));
Then put this after the default MapRoute. The important part is the "*" before the filepath, tells MVC to send anything following this as part of the filepath RouteData. So in the GetHttpHandler() method I can get the full path by using this;
string fp = requestContext.RouteData.Values["filepath"] as string;
Woot!

Can't you treat the entire path as one route parameter? Like so:
routes.Add(
"ImageRoute",
"/images/{path}",
new { controller = "Image", action = "Image" }
);
And then access the entire path in the ActionResult Image(string path) { } action method?

Related

Reference variables in a separate less file using SquishIt (support dynamic LESS content)?

We are using bootstrap for our project in MVC4. So far, we were referencing the bootstrap.less file in our main layout page and it worked great. However, a new requirement has come along that requires us to have customized look for each of our department pages (each department has its own layout that use the main layout)
bootstrap.less has this structure:
#import variables.less // defines all the variables
#import others // all imports like mixins.less, reset.less etc
since we need to inject our variable override, we created another less file:
bootstrap-without-variables.less //contains all the imports without the variables.less from bootstrap.less
The reason for this separation is to inject our variable override so that we can customize the bootstrap styles for our pages.
We are using SquishIt to bundle the less files into a bundle.
Here is the Bundle:
Bundle.Css()
.Add("~/bootstrap/variables.less")
.Add("~/variable-override.less") // custom override
.Add("~/bootstrap/bootstrap-without-variables.less")
.MvcRender("styles_combined_#.js");
This does not work at all. If I remove the variables.less and reference that in bootstrap-without-variables.less (which now becomes similar to bootstrap.less), it works perfectly fine.
The issue, I think, is that each file is evaluated and converted to css independently before combining them together.
Is there a way to tell the bundler to first bundle the files into one and then to evaluate and convert to css or a better solution to this problem?
Like mentioned by AlexCuse, there was no way for me to do what I mentioned above using only SquishIt. However, as mentioned in https://github.com/jetheredge/SquishIt/issues/170, there is an overload to AddString() that lets you add dynamic less content.
For example,
.AddString("LessString",".less") // .less being the extension
This works perfectly fine as long as the LessString does not contain any imports (#import). So, I downloaded the source from https://github.com/jetheredge/SquishIt and started diving into the code. Going through the code I found that the content loaded through AddString() has the CurrentDirectory set to the path of my IIS ("c:\windows\system32\inetsrv"). As a result of which, the imports were throwing
FileNotFoundException (You are importing a file ending in .less that
cannot be found.)
So, I needed a way to set the current directory (reference location from where my imports will be searched)
Here is what I did:
STEP1: Extended Asset to have a property called CurrentDirectory
internal string CurrentDirectory { get; set; }
STEP2: Added a third optional parameter to the AddString() overload
public T AddString(string content, string extension, string currentDirectory = null)
{
return AddString(content, extension, true, currentDirectory);
}
STEP3: Updated the AddString() to add the current directory to the Asset
T AddString(string content, string extension, bool minify, string currentDirectory = null)
{
if (bundleState.Assets.All(ac => ac.Content != content))
bundleState.Assets.Add(new Asset { Content = content, Extension = extension, Minify = minify, CurrentDirectory = currentDirectory });
return (T)this;
}
STEP4: Modify the PreprocessArbitary (For Release) on BundleBase to set the current directory
protected string PreprocessArbitrary(Asset asset)
{
if (!asset.IsArbitrary) throw new InvalidOperationException("PreprocessArbitrary can only be called on Arbitrary assets.");
var filename = "dummy." + (asset.Extension ?? defaultExtension);
var preprocessors = FindPreprocessors(filename);
return asset.CurrentDirectory != null ?
directoryWrapper.ExecuteInDirectory(asset.CurrentDirectory, () => MinifyIfNeeded(PreprocessContent(filename, preprocessors, asset.Content), asset.Minify)) :
MinifyIfNeeded(PreprocessContent(filename, preprocessors, asset.Content), asset.Minify);
}
For Debug, modify the RenderDebug to set the current directory
if (asset.IsArbitrary)
{
var filename = "dummy" + asset.Extension;
var preprocessors = FindPreprocessors(filename);
var processedContent = asset.CurrentDirectory != null ?
directoryWrapper.ExecuteInDirectory(asset.CurrentDirectory, () => PreprocessContent(filename, preprocessors, asset.Content)) :
PreprocessContent(filename, preprocessors, asset.Content);
sb.AppendLine(string.Format(tagFormat, processedContent));
}
Here is my how I add dynamic or static less files now:
.AddString("#import 'content/bootstrap/variables.less';", ".less", AppDomain.CurrentDomain.BaseDirectory)
For my above requirement, I read the variables.less into a string builder, then add my variable-override.less and finally add the bootstrap-without-variables.less to the string builder.
It has worked for me so far. I tested following scenarios:
normal less files with imports, e.g. .Add("~/content/styles.less")
inline less without imports, e.g. .AddString(LessString, ".less")
dynamic less files with imports, e.g. .AddString("#import content/bootstrap/variables.less';", ".less", AppDomain.CurrentDomain.BaseDirectory)
I'll try to do a pull request soon. I hope this helps people looking to support dynamic LESS content with imports.
You are right, SquishIt does the less processing before combining files. There is no way to do what you ask using only SquishIt, but I suppose you could combine the files on your own on the way in (use the .AddString method to add your resulting .less content to the bundle). One of the alternatives may suit your needs better, I'm not sure.

Dynamic sitemap, database driven

I've been struggling with this for a couple of days now. Can't find any good example, or an example that I understand.
Background:
I own a small blog platform for user to blog.
Each user gets their own subdomain and for now there is no sitemap available. Not good.
I want to create some kind of dynamic sitemap, where all sitemapnodes is retreived from the database. The sitemap will be used only for the search engine spiders.
System: ASP.NET, mySQL.
The sitemap is pure XML. So I need in some way to create an ASPX file that return xml-data instead of html.
And I need to somehow redirect the web.sitemap to that dynamic file.
I have never worked with XML, and I dont know how to create a file that creates XML data. So i dont even know what to search for.
I don't want any static sitemap file to be stored on the server. Everything should be created on the fly.
So. Please. If you can give me some advise about XML, any example on the internet, or just what to search for.
My main questions:
1.
How to create XML output from aspx file?
2.
How do I "inform" the system, and search engine crawlers that the file to crawl is "/sitemap.aspx"
ThankS!
I looked into MvcSiteMapProvider.MVC5 and I could not get it to work. First of all it modified my Web.config to the point that my css and js files were getting a 404 not found when running my web app.
With the time I spent getting MvcSiteMapProvider to work I could have just wrote my own.
So... here is my own dumbed down version of generating a sitemap xml.
The only thing is you have to specify your routes manually. I haven't added reflection yet to go through each controller and pull out each action.
The data-driven piece works very well though.
In your Home controller add the action Sitemap and the private helper methods.
GetRouteUrls is the manually added controller/action routes.
GetDynamicUrls builds the data-driven Urls. In my example I have a LiquidsController and a Details(string id) action.
public ActionResult Sitemap()
{
var xml = new XDocument(
new XDeclaration("1.0", "utf-8", null),
new XElement("urlset",
new XAttribute("xmlns", "http://www.sitemaps.org/schemas/sitemap/0.9")
, GetRouteUrls()
, GetDynamicUrls()
)
);
return new XmlActionResult(xml);
}
private List<XElement> GetDynamicUrls()
{
var result = new List<XElement>();
using (var db = new ApplicationDbContext())
{
var liquids = db.Liquids.ToList();
foreach (var liquid in liquids)
{
result.Add(LocUrl("Liquids", "Details", liquid.FriendlyId));
}
}
return result;
}
private List<XElement> GetRouteUrls()
{
var result = new List<XElement>();
result.Add(LocUrl("Account", "Register"));
result.Add(LocUrl("Account", "Login"));
result.Add(LocUrl("Home", "Index"));
result.Add(LocUrl("Home", "About"));
result.Add(LocUrl("Home", "Contact"));
result.Add(LocUrl("Home", "TermsOfService"));
result.Add(LocUrl("Home", "PrivacyStatement"));
result.Add(LocUrl("Liquids", "Index"));
result.Add(LocUrl("Vendors", "Index"));
result.Add(LocUrl("Hardware", "Index"));
return result;
}
private XElement LocUrl(string controller, string action, string id = null)
{
if (!string.IsNullOrEmpty(id))
action = string.Format("{0}/{1}", action, id);
var baseUri = string.Format("{0}://{1}{2}", Request.Url.Scheme, Request.Url.Authority, Url.Content("~"));
return new XElement("url",
new XElement("loc", string.Format("{0}{1}/{2}", baseUri, controller, action))
);
}
I then added a route so I could access the sitemap doing /sitemap
routes.MapRoute(name: "sitemap", url: "sitemap", defaults: new {controller = "Home", action = "Sitemap"});
The XmlActionResult return type can be found here:
Return XML from a controller's action in as an ActionResult?

SEO URL rewriting ASP.NET

I already have an ASP.NET Web Site
I want to change my site to be more SEO url friendly.
I want to change ex. this site:
www.mydomain.aspx?articleID=5
to:
www.mydomain/article/learningURLrewrite
- articlename needs to be read from DB
How do I accomplish this?
I have already tried with some articles from Google which mentions IhttpModule without any luck.
My goal is to have a class responsible for redirecting based on folderpath(like this):
string folderpath = "my folderpath" (could be articles, products etc.)
string id = Request.QueryString["id"].ToString();
if(folderpath.equals("articles"))
{
string name = //find name from id in DB
//redirect user to www.mydomain/article/name
}
if(folderpath.equals("products"))
{
string name = //find name from id in DB
//redirect user to www.mydomain/products/name
}
Also I want to remove the aspx extension
You can use routing with ASP.NET WebForms too.
The steps are:
Add the route (or routes) at application start.
//In Global.asax
void Application_Start(object sender, EventArgs e)
{
RouteTable.Routes.MapPageRoute("My Routename", "{*name}", "~/Article.aspx");
}
Create the Article.aspx as a normal webform
In the code for Article.aspx, you can access the url path like this:
public void Page_Load(object sender, EventArgs e)
{
var thePath = RouteData.Values["name"];
// Lookup the path in the database...
}
This posting tells you exactly how to use asp.net 4's routing engine - give it a whirl - if you have a specific problem in implementing it let us know.
http://weblogs.asp.net/dotnetstories/archive/2011/01/03/routing-in-asp-net-4-0-web-forms.aspx
Since you need specific parameter usage, you can define the parameters to get sent to your page. For that see:
http://msdn.microsoft.com/en-us/library/cc668177.aspx
and
How to: Access URL Parameters in a Routed Page
If you are using ASP.NET 4, then you should look into URL Routing. You would end up setting up custom routes like so:
routes.MapPageRoute(
"View Article", // Route name
"Articles/{*ArticleName}", // Route URL
"~/Articles.aspx" // Web page to handle route
);
And you write out the new links like so:
Page.GetRouteUrl("View Article", new { ArticleName= NAMEFROMDATABASE });
Unfortunately I won't give you a summary of how to build your entire site, but 2 really good places to start are an article by Scott Gu, and one on 4 Guys.
If you are using .net 3.5 or less then you can use these
http://urlrewriting.net
http://urlrewriter.net
I use the second one, in all my projects made in .net 3.5
if using .net 4.0 then you can do these
URL routing ( I think it does not support sub domain rewriting)
URL Rewrite 2.0 ( works with IIS 7 only)
UPDATE
Add these lines below the appSettings tag
<rewriter configSource="URLRewriter.config"/>
Then create separate file named as URLRewriter.config
And in that you can write like this (Add processing stop for the files not to be rewritten i.e images and js, etc)
<rewrite url="^(/.+(\.gif|\.png|\.jpg|\.ico|\.pdf|\.css|\.js|\.flv|\.eot|\.svg|\.ttf|\.woff|\.txt|\.doc|\.docx|\.pdf|\.xls|\.xlsx|\.xml)(\?.+)?)$" to="$1" processing="stop" />
<rewrite url="~/article/([^/.]+)" to="~/articledetail.aspx?articlename=$1" />
Then you would get the article name in the query string like this
string articlename = Request.QueryString["articlename"];
And the menu or other location of the site where you want to put the link to article, you can add an the AppSettings so that later on if you want to change the url pattern you can change it easily from the configs only,
<add key ="ArticalDetailsURL" value="/article/{0}" />
Then, in the page you can do like this
string articleName = "TestArticle";
lnkMenuLink.NavigateUrl = string.Format(ConfigurationSettings.AppSettings["ArticalDetailsURL"], articleName);
Thanks and Regards,
Harsh Baid

Asp.Net single control render for AJAX calls

I'm trying to implement something similar to this or this.
I've created a user control, a web service and a web method to return the rendered html of the control, executing the ajax calls via jQuery.
All works fine, but if I put something in the user control that uses a relative path (in my case an HyperLink with NavigateUrl="~/mypage.aspx") the resolution of relative path fails in my developing server.
I'm expecting:
http://localhost:999/MyApp/mypage.aspx
But I get:
http://localhost:999/mypage.aspx
Missing 'MyApp'...
I think the problem is on the creation of the Page used to load the control:
Page page = new Page();
Control control = page.LoadControl(userControlVirtualPath);
page.Controls.Add(control);
...
But I can't figure out why....
EDIT
Just for clarity
My user control is located at ~/ascx/mycontrol.ascx
and contains a really simple structure: by now just an hyperlink with NavigateUrl like "~/mypage.aspx".
And "mypage.aspx" really resides on the root.
Then I've made up a web service to return to ajax the partial rendered control:
[ScriptService]
[WebService(Namespace = "http://tempuri.org/")]
[WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
public class wsAsynch : System.Web.Services.WebService
{
[WebMethod(EnableSession = true)]
public string GetControl(int parma1, int param2)
{
/* ...do some stuff with params... */
Page pageHolder = new Page();
UserControl viewControl = (UserControl)pageHolder.LoadControl("~/ascx/mycontrol.ascx");
Type viewControlType = viewControl.GetType();
/* ...set control properties with reflection... */
pageHolder.Controls.Add(viewControl);
StringWriter output = new StringWriter();
HttpContext.Current.Server.Execute(pageHolder, output, false);
return output.ToString();
}
}
The html is correctly rendered, but the relative path in the NavigateUrl of hyperlink is incorrectly resolved, because when I execute the project from developing server of VS2008, the root of my application is
http://localhost:999/MyApp/
and it's fine, but the NavigateUrl is resolved as
http://localhost:999/mypage.aspx
losing /MyApp/ .
Of Course if I put my ascx in a real page, instead of the pageHolder instance used in the ws, all works fine.
Another strange thing is that if I set the hl.NavigateUrl = Page.ResolveUrl("~/mypage.aspx") I get the correct url of the page:
http://localhost:999/MyApp/mypage.aspx
And by now I'll do that, but I would understand WHY it doesn't work in the normal way.
Any idea?
The problem is that the Page-class is not intented for instantiating just like that. If we fire up Reflector we'll quickly see that the Asp.Net internals sets an important property after instantiating a Page class an returning it as a IHttpHandler. You would have to set AppRelativeTemplateSourceDirectory. This is a property that exists on the Control class and internally it sets the TemplateControlVirtualDirectory property which is used by for instance HyperLink to resolve the correct url for "~" in a link.
Its important that you set this value before calling the LoadControl method, since the value of AppRelativeTemplateSourceDirectory is passed on to the controls created by your "master" control.
How to obtain the correct value to set on your property? Use the static AppDomainAppVirtualPath on the HttpRuntime class. Soo, to sum it up... this should work;
[WebMethod(EnableSession = true)]
public string GetControl(int parma1, int param2)
{
/* ...do some stuff with params... */
var pageHolder = new Page() { AppRelativeTemplateSourceDirectory = HttpRuntime.AppDomainAppVirtualPath };
var viewControl = (UserControl)pageHolder.LoadControl("~/ascx/mycontrol.ascx");
var viewControlType = viewControl.GetType();
/* ...set control properties with reflection... */
pageHolder.Controls.Add(viewControl);
var output = new StringWriter();
HttpContext.Current.Server.Execute(pageHolder, output, false);
return output.ToString();
}
The tildy pust the path in the root of the app, so its going to produce a the results you are seeing. You will want to use:
NavigateUrl="./whatever.aspx"
EDIT:
Here is a link that may also prove helpful...http://msdn.microsoft.com/en-us/library/ms178116.aspx
I find the /MyApp/ root causes all sorts of issues. It doesn't really answer your question 'why is doesn't work the normal way', but do you realize you can get rid of the /MyApp/ and host your website at http:/localhost/...?
Just set Virtual Path in the website properties to '/'.
This clears everything up, unless of course you are trying to host multiple apps on the development PC at the same time.
It might be that the new page object does not have "MyApp" as root, so it is resolved to the server root as default.
My question is rather why it works with Page.ResolveUrl(...).
Maybe ResolveUrl does some more investigation about the location of the usercontrol, and resolves based on that.
Weird, I recreated the example. The hyperlink renders as <a id="ctl00_hlRawr" href="Default.aspx"></a> for a given navigation url of ~/Default.aspx. My guess is that it has something to do with the RequestMethod. On a regular page it is "GET" but on a webservice call it is a "POST".
I was unable to recreate your results with hl.NavigateUrl = Page.ResolveUrl("~/mypage.aspx")
The control always rendered as <a id="ctl00_hlRawr" href="Default.aspx"></a> given a virtual path. (Page.ResolveUrl gives me "~/Default.aspx")
I would suggest doing something like this to avoid the trouble in the future.
protected void Page_Load(object sender, EventArgs e)
{
hlRawr.NavigateUrl = FullyQualifiedApplicationPath + "/Default.aspx";
}
public static string FullyQualifiedApplicationPath
{
get
{
//Return variable declaration
string appPath = null;
//Getting the current context of HTTP request
HttpContext context = HttpContext.Current;
//Checking the current context content
if (context != null)
{
//Formatting the fully qualified website url/name
appPath = string.Format("{0}://{1}{2}{3}",
context.Request.Url.Scheme,
context.Request.Url.Host,
(context.Request.Url.Port == 80 ? string.Empty : ":" + context.Request.Url.Port),
context.Request.ApplicationPath);
}
return appPath;
}
}
Regards,
It is hard to tell what you are trying to achieve without posting the line that actually sets the Url on of the HyperLink, but I think I understand your directory structure.
However, I have never run into a situation that couldn't be solved one way or another with the ResolveUrl() method. String parsing for a temporary path that won't be used in production is not recommended because it will add more complexity to your project.
This code will resolve in any object that inherits from page (including a usercontrol):
Page page = (Page)Context.Handler;
string Url = page.ResolveUrl("~/Anything.aspx");
Another thing you could try is something like this:
Me.Parent.ResolveUrl("~/Anything.aspx");
If these aren't working, you may want to check your IIS settings to make sure your site is configured as an application.

Getting the mime w/o using urlmon

I was using urlmon to find the MIME of files however it didnt go well when i couldn't get the correct mime of css files and more SWFs. What can i use to get the file mime?
Hmm, I am not sure I completely understand your question, but if you want to do some sort of look up against a master list you can look at the IIS Metabase
using (DirectoryEntry directory = new DirectoryEntry("IIS://Localhost/MimeMap")) {
PropertyValueCollection mimeMap = directory.Properties["MimeMap"];
foreach (object Value in mimeMap) {
IISOle.MimeMap mimetype = (IISOle.MimeMap)Value;
//use mimetype.Extension and mimetype.MimeType to determine
//if it matches the type you are looking for
}
}

Resources