Broken Images on IIS7 - asp.net-mvc

I have a ASP.NET MVC site and it works just fine when I run it locally. Once I deploy that site to IIS 7 all links to resources are broken (ie. script files, images, css files). Could this be a route issue or would it be an IIS setting?
Here are my routes:
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("elmah.axd");
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute(
"Search",
"Basic/Page/{page}",
new { controller = "Search", action = "Basic" }
);
routes.MapRoute(
"Default", // Route name
"{controller}/{action}/{id}", // URL with parameters
new { controller = MVC.Welcome.Name, action = MVC.Welcome.Actions.Index, id = "" } // Parameter defaults
);
}
EDIT:
I reference all content using T4MVC template. The template is correct when it specifies the paths with ~/content/. The problem is that when the html is generated the output doesn't include the "~" it is just /content/.
<img src="<%= Links.Content.Images.logo_png %>" alt="Logo" />
<img src="/Content/Images/logo.png" alt="Logo" />
NOTE:
The issue actually was that there is something wrong with this line in web.config. Turns out the January 1st 2011 is not a Friday but Saturday. For some reason it still didn't like that line.
<clientCache httpExpires="Fri, 1 Jan 2011 15:30:00 UTC" cacheControlMode="UseExpires"/>
Changing it to this works just fine;
<clientCache cacheControlMode="UseExpires" httpExpires="Tue, 19 Jan 2038 03:14:07 GMT" />
Adding it here in hopes that it helps others with this issue.
Thanks!

It's unlikely to be a route or IIS setting. The times I've seen this is generally because either the resources are unavailable, ie not there.
Also sometimes security on the folder(s) you are trying to access has been set and the default .net user has not been granted access.
Paths to the resources are not coded correctly. using ~/content instead of /content or even ../../..etc might help.

Make sure the build actions are set to Content.

Try checking your folder permissions - are you in a non-standard folder (not wwwroot)? Make sure that the IIS_IUSRS group has Read & Execute permissions on the folder and subfolders. If that doesn't work, try changing the permissions to temporarily give Full Control to Everyone, just to see if it's a permissions issue at all.

Not sure I understand the issue. It would be wrong for T4MVC to output the ~/ path to the client, since ~/ is a server side syntax that browsers don't understand. Note that you can change this processing if you like by going to T4MVC.settings.t4, which has:
// You can change the ProcessVirtualPath method to modify the path that gets returned to the client.
// e.g. you can prepend a domain, or append a query string:
// return "http://localhost" + path + "?foo=bar";
private static string ProcessVirtualPathDefault(string virtualPath) {
// The path that comes in starts with ~/ and must first be made absolute
string path = VirtualPathUtility.ToAbsolute(virtualPath);
// Add your own modifications here before returning the path
return path;
}
So you can make it return whatever you want, but I don't think returning the ~/ path will help you.
I may be misunderstanding the issue a bit.

Go to your Site or Web Application -> Authentication -> Enable Anonymous Authentication. If this works, you can leave like that if you are fine with it, or properly adjust the permissions.

Related

ASP.NET Core 3.1 MVC Routing issue with IIS Default Web Site

New information:
After much messing around with trying to manipulate the URL I almost got it working but not quite. Then I discovered that it works without any coding changes if my home page url is \\localhost\ABIAdmin\Home. But it starts as \\localhost\ABIAdmin, and I have link in my _Layout to bring me there and it also comes up as \\localhost\ABIAdmin (without the \Home). It's easy enough to require our users to provide the full url with home in it, but I need the link to also provide \Home in it. Here's the html for the Home link:
<a class="nav-link text-dark" asp-area="" asp-controller="Home" asp-action="Index">Dashboard</a>
So now the question is how do I get \Home in the url from the link, and if possible on startup? Can this be addressed through IIS, or through my endpoints?
I have an ASP.NET Core 3.1 MVC application with Razor Pages which works fine when I deploy it to IIS if I do not use the Default Web Site, or if I run the application as an exe. All of my navigation works, and all of my CRUD operations work (I'm using Syncfusion's DataGrid). However, if I deploy to the Default Web Site I run into what seem to be routing issues. Please note the following:
I added a folder under c:\inetpub\wwwroot called ABIAdmin, which contains my core app.
Relevant code from Startup/ConfigureServices: services.AddControllersWithViews();
Relevant code from Startup/Configure:
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseAuthorization();
app.UseCookiePolicy();
app.UseSession();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
endpoints.MapControllerRoute(
name: "Privacy",
pattern: "{controller=Privacy}/{action=Privacy}/{id?}");
});
I have about 80 controllers not including the Privacy and Home controllers. There is no routing information in any of the controllers.
I tried messing with adding virtual directories but that did not help.
When I navigate to the Campaigns page from the Privacy page it forms a proper URL, e.g., http://localhost:5000/ABIAdmin/Campaigns, where ABIAdmin is the name of the site under the Default Web Site. But if I navigate from the Home page I get a 404 error. The requested URL is formed as http://localhost:5000/Campaigns, but it is missing the "/ABIAdmin", and the physical path is wrong too: C:\inetpub\wwwroot\Campaigns. When I successfully navigate from the Privacy page, the data is retrieved, but if I try to perform an Add/Update/Delete operation it just hangs. I believe this is because whatever URL the grid is forming is wrong. I don't think this is the fault of the Syncfusion grid.
So the question is, why does the Default Web Site behave differently then a standalone web site in this situation? We will be deploying this to multiple customers, and some will want it under the Default Web Site.
Any help would be appreciated. :)
It's looks like I found an answer. I have 3 scenarios to handle, running from VS, running from a standalone website, and running from an application under the Default Web Site. I could not figure out a way to tell in my _Layout.cshtml whether it is running under the Default Web Site so I'm using a configuration parameter to tell me that. I can tell whether I'm running from VS by the name of the application pool (from VS it's "ABIAdminApp AppPool"). I can also tell if I'm coming from the Home page now (that was the missing link), by checking Context.Request.Path. Given that info I can conditionally add the name of my application, ABIAdminApp. Note in the original post I may have referred to this as ABIAdmin. I will consider making the name of the app a configuration parameter as well.
#{
#using ABIAdminApp.Classes;
#using Microsoft.Extensions.Configuration;
#inject IConfiguration Configuration;
string appPoolId = System.Environment.GetEnvironmentVariable("APP_POOL_ID");
string appName = "";
if (appPoolId != "ABIAdminApp AppPool" && Context.Request.Path.ToString() == "" && Extensions.UseDefaultWebsite(Configuration)) { appName = "ABIAdminApp" + "/"; } else { appName = ""; }
string anchor_template = "<a href='" + appName + "${URL}'>${FriendlyName}</a>";
}
So that solves my navigation problems. I also had to make changes for the Syncfusion grid updates. I had to conditionally add the app name and used ViewBag for that purpose with dynamic values for insertUrl, updateUrl, and removeUrl.
So in my Index method of the controller for Campaigns I had this code (preceded by logic to determine whether or not to add the app name):
ViewBag.InsertUrl = AppName + "/Campaigns/Insert";
ViewBag.UpdateUrl = AppName + "/Campaigns/Update";
ViewBag.RemoveUrl = AppName + "/Campaigns/Delete";
And this code in my view, Campaigns.cshtml:
<e-data-manager url="Campaigns/Campaigns" adaptor="UrlAdaptor" insertUrl="#ViewBag.InsertUrl" updateUrl="#ViewBag.UpdateUrl" removeUrl="#ViewBag.RemoveUrl"></e-data-manager>

Favicon with ASP.net and MVC (Routes)

I'm getting 404 errors as there is no /favicon.ico. The actual icon is located as /content/favicon.ico.
I've set this to my html pages:
<link rel="icon" href="#Url.Content("~/content/favicon.ico")" type="image/x-icon" />
It works, but some browsers seem to ignore it, or look for /favicon.ico anyway.
So, what I'm asking for is an ASP route that turns "/favicon.ico" into "/content/favicon.ico".
Some browsers look for favicons in the root because thats where they used to live by convention.
Since routes are not used for static content like a favicon image, adding a route won't help. You could rewrite these requests using the IIS rewriting module or implement a custom handler for this specific case, but just putting the favicon in the root a lot less hassle - keep it simple.
I solved it by simply rewriting the target location directly in my code. Without any need of external re-write mods, as suggested on similar questions.
// Register event-listener in Web-App constructor
this.PreRequestHandlerExecute += new EventHandler(MvcApplication_PreRequestHandlerExecute);
void MvcApplication_PreRequestHandlerExecute(object sender, EventArgs e)
{
string originalPath = HttpContext.Current.Request.Path.ToLower();
if (originalPath == "/favicon.ico")
{
Context.RewritePath("~/content/favicon.ico");
}
}
MSDN documentation: http://msdn.microsoft.com/en-us/library/system.web.httpserverutility.mappath(v=vs.110).aspx

ASP.net MVC SPA routing

I'm planning to build a SPA with asp.net MVC4 but I'm not quite sure how I have to Setup my Project because of the Routing. Most SPA's work with hashrouting like this mypage/#/News/today for instance.
What would happen if the browses directly to mypage/News/today if I haven't specified a Controller named News with an action today?
The App should handle both types of Routing, how can I achieve this?
Do I have to build my App in a classic way, like Adding several Controllers with appropriate Actions and views and also build a clientside MVC structure with knockout, jquery etc?
You'll have to let all routes to "pages" fall through to let your SPA handle them (including essentially fake 404s if it's not to a real page in your SPA), but at the same time, need to make sure that you get the correct responses for API calls and/or file requests.
Below is the setup I have (I am using Vue as the js framework but that doesn't matter much for this, and not at all for the server-side piece).
First, add this to your Startup.cs, in addition to your default route setup:
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
...
app.Use(async (context, next) =>
{
await next();
var path = context.Request.Path.Value;
// If there's no available file and the request doesn't contain an extension, we're probably trying to access a page
if (context.Response.StatusCode == (int)HttpStatusCode.NotFound && !Path.HasExtension(path) && !path.StartsWith("/api"))
{
context.Request.Path = "/Home/SpaRedirect"; // attempts to redirect to the URL within the SPA
context.Response.StatusCode = (int)HttpStatusCode.OK; // Make sure we update the status code, otherwise it returns 404
await next();
}
});
...
}
So the newly added SpaRedirect to HomeController looks like this, and just stores the requested URL in ViewData...
public IActionResult SpaRedirect()
{
ViewData["RequestUrl"] = HttpContext.Request.Path;
return View("Index");
}
Then in Index.cshtml, just capture that requested url in session storage so we have it available on the client-side:
<script src="~/dist/main.js" asp-append-version="true">
sessionStorage.setItem("redirectAttempt", #ViewData["RequestUrl"]);
</script>
Then in your boot script file (the entry-point for your SPA), add something like:
let redirectAttemptUrl = sessionStorage.getItem("redirectAttempt");
if (redirectAttemptUrl) {
router.push(redirectAttemptUrl);
sessionStorage.removeItem("redirectAttempt");
}
Which just checks for the presence of a requested url, and then the SPA's router attempts to navigate to it (in the example above it is a vue-router), then removes it from storage.
So this way, if a user attempts to navigate directly to a URL by entering it in the url bar (or via a bookmark) the app will load and take them to the right place, IF it exists... which takes us to the last piece...
Finally, you have to handle "404s" within your SPA, which is done by adding a catch-all route to your routes defs that takes user to a 404 component page you set up, which for Vue would look like this:
// adding an explicit 404 path as well for programmatically handling when something is not found within the app, i.e. return this.$router.push('/404')
{ path: '/404', component: NotFound, name: '404', alias: '*' }, // remove alias to not show the actual url that resulted in our little 404 here
{ path: '*', redirect: '/404' }, // this is the catch-all path to take us to our 404 page
Caveat: I'm no expert so could be missing something, would love additional comments on how to improve this. One thing that this doesn't handle is if the user is ALREADY in the SPA and for some reason edits the URL directly to navigate to someplace else, it would still trigger a server call and full reload, which ideally wouldn't be the case, but this is a pretty trivial issue I'd say.

ASP.NET MVC 3: RouteExistingFiles = true seems to have no effect

I'm trying to understand how RouteExistingFiles works.
So I've created a new MVC 3 internet project (MVC 4 behaves the same way) and put a HTMLPage.html file to the Content folder of my project.
Now I went to the Global.Asax file and edited the RegisterRoutes function so it looks like this:
public static void RegisterRoutes(RouteCollection routes)
{
routes.RouteExistingFiles = true; //Look for routes before looking if a static file exists
routes.MapRoute(
"Default", // Route name
"{controller}/{action}/{id}", // URL with parameters
new {controller = "Home", action = "Index", id = UrlParameter.Optional} // Parameter defaults
);
}
Now it should give me an error when I'm requesting a localhost:XXXX/Content/HTMLPage.html since there's no "Content" controller and the request definitely hits the default pattern. But instead I'm seeing my HTMLPage.
What am I doing wrong here?
Update:
I think I'll have to give up.
Even if I'm adding a route like this one:
routes.MapRoute("", "Content/{*anything}", new {controller = "Home", action = "Index"});
it still shows me the content of the HTMLPage.
When I request a url like ~/Content/HTMLPage I'm getting the Index page as expected, but when I add a file extenstion like .html or .txt the content is shown (or a 404 error if the file does not exist).
If anyone can check this in VS2012 please let me know what result you're getting.
Thank you.
To enabling routing for static files you must perform following steps.
In RouteConfig.cs enable routing for existing files
routes.RouteExistingFiles = true;
Add a route for your path ( Make sure specialized path are above generalized paths)
routes.MapRoute(
name: "staticFileRoute",
url: "Public/{file}/",
defaults: new { controller = "Home", action = "SomeAction" }
);
Next configure your application, so that request for static files are handeled by "TransferRequestHandler".In Webconfig under system.webServer>handlers add following entry.
<add name="MyCustomUrlHandler2" path="Public/*" verb="GET" type="System.Web.Handlers.TransferRequestHandler" preCondition="integratedMode,runtimeVersionv4.0" />
The value of 'path' can be more generic or specific depending on your requirement. But i prefer it to be always very specific as per one's need. Keeping it very generic will block serving of other site specific resources such as .js or css files. For example if above is set as path="*", then request for even the css (inside the content folder) which is responsible for how your page would look will also end up in your Controller's action. Something that you will not like.
Visual Studio 2012 uses IIS Express. You need to tell IIS not to intercept requests for disk files before they are passed to the MVC routing system. You need set preCondition attribute to the empty string in config file:
<add name="UrlRoutingModule-4.0" type="System.Web.Routing.UrlRoutingModule"
preCondition="" />
In Win7/8 you can find config file on this path: %userprofile%\Documents\IISExpress\config\applicationhost.config
The RouteExistingFiles doesn't keep files from being viewed if there is no route for them, it just checks the routes before checking if the file exists. If there is no matching route, it will continue to check if there is a matching file.

ASP.NET MVC not using controller for explicit file route in IIS7

Consider a StaticResourceController that locates and serves files.
I've set up an explicit route for "favicon.ico" that will handle the request for this file using StaticResourceController:
routes.MapRoute(
"favicon",
"favicon.ico",
new { controller = "StaticResource", action = "Get", file = "favicon.ico", area="root"},
new[] { "Dimebrain.Mvc.Controllers" }
);
In IIS6 the expected result occurs when making a request for http://localhost:8080/favicon.ico.
Unfortunately when I deploy to IIS7 http://localhost/favicon.ico returns an IIS-generated 404, presumably because it's actually looking for the favicon.ico in the web root folder, where it doesn't exist.
I have enough happening in StaticResourceController that this isn't a good thing for my application, especially since it is multi-tenant and the favicon.ico file can change. I've set my web server modules to handle every request and override the RouteCollection to disregard file checks with RouteExistingFiles.
Why is the UrlRoutingModule getting in my way in IIS7 and forcing serving the static file from disk (404)?
In case anyone else runs into this problem, the solution is you need you to let MVC know not to process requests in folders where your actual static files live:
// Make sure MVC is handling every request for static files
routes.RouteExistingFiles = true;
// Don't process routes where actual static resources live
routes.IgnoreRoute("content/{*pathInfo}");
routes.IgnoreRoute("scripts/{*pathInfo}");
routes.IgnoreRoute("areas/admin/content/{*pathInfo}");
routes.IgnoreRoute("areas/admin/scripts/{*pathInfo}");
In adiition to Daniel Crenna's answer, you need to add in web.confug file in system.webServer section:
<modules runAllManagedModulesForAllRequests="true"/>

Resources