ASP.NET MVC VirtualPathProvider not working under IIS 6 - asp.net-mvc

By ASP.NET MVC plugin architecture, Plug-in architecture for ASP.NET MVC
I have separated DLL(plugin) which contains the views, css and javascript files in the resources. So my own VirtualPathProvider will load the content out from the DLL if that is for the plugin. It works all fine during development. But It appears not working once I deployed it in IIS. (I mapped the whidcard in IIS 6 and the views are showing)
I have registered my VirtualPathProvider in global.asax as
protected void Application_Start()
{
RegisterRoutes(RouteTable.Routes);
HostingEnvironment.RegisterVirtualPathProvider(new MyVirtualPathProvider());
}
For example.
http://localhost/Plugin/MyPlugin.dll/Styles.MyStyles.css
This should be loaded from the plugin.dll but IIS returns 404.
I guess the static files are all handled by the IIS and not went through asp.net and my VirtualPathProvider ? Is there way to get around this? Please shed some light.
Thanks in advance.

If this is IIS 6 you will need a wildcard mapping. See this blog post from Phil Haack.

I've found the workaround by adding the staticFileHandler in the web.config httpHandlers element.
<add verb="GET,HEAD,POST" path="*" type="System.Web.StaticFileHandler" validate="true" />

I've had a number of problems getting an external compiled library containing resources and controllers to work in our MVC environment. It's used across multiple projects and different errors have surfaced in different projects so here's all the things I had to do (so far) to ensure static file handling works:
Include StaticFileHandler in web.config, e.g.:
<add verb="GET,HEAD" path="*.js" name="Static for js" type="System.Web.StaticFileHandler" />
Ensure Static items are ignored in routing:
routes.IgnoreRoute("{*staticfile}", new { staticfile = #".*\
.(css|js|gif|jpg)(/.*)?" });
Register a Virtual Path Provider, e.g.:
System.Web.Hosting.HostingEnvironment.RegisterVirtualPathProvider(new EmbeddedResourceVirtualPathProvider.Vpp(assemblies.ToArray())
{
//you can do a specific assembly registration too. If you provide the assemly source path, it can read
//from the source file so you can change the content while the app is running without needing to rebuild
//{typeof(SomeAssembly.SomeClass).Assembly, #"..\SomeAssembly"}
});
Not required for static files but worth mentioning are what was needed to get the views / controllers working, which was adding MVCContrib and registering the embedded view engine:
PortableAreaRegistration.RegisterEmbeddedViewEngine();

Related

How to direct to an .aspx web form from a controller (MVC 5 framework)?

I have a MVC project which needs to incorporate SSRS reports as a part of the application. I've not done this before so I read up a couple of blogs, msdn pages etc. But I haven't come across a concrete solution yet.
Here's my code for the controller
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
namespace AppName.Controllers
{
public class ReportsController : Controller
{
//
// GET: /Reports/
public ActionResult Index()
{
var reportParameters = new Dictionary<string, string>();
reportParameters.Add("First_Name", "TEST");
Session["reportParameters"] = reportParameters;
return Redirect("../Views/Reports/Index.aspx");
}
}
}
The last line 'return Redirect' does not redirect to the desired page , instead it gives me a 'can't find the page' error and the link displayed is as follows:
"/localhost:port#/Error/PageNotFound?aspxerrorpath=/Views/Reports/Index.aspx"
I have even tried 'RedirectRoute' and 'RedirectToAction', but none of them work. I am using a web form instead of an MVC view because that's what is presribed by many SSRS tutorials in order to achieve what I want. I know the redirect line has worked in the past for most folks. I'm surely missing something here. Any help would be greatly appreciated!
Thanks,
H
There is just a little tweak to have it working: instead of using a relative URL, do use an URL relative to the application root ~, like this:
return Redirect("~/Views/Reports/Index.aspx");
This will generate the current redirect URL.
EDIT
There can be a second problem in this case. The Views folder is somewhat special, because it has its own web.config which can make it impossible to get the files inside it. So you also need to move your .aspx page to somewhere else and update the Redirect accordingly.
To be more precise, if you look inside the web.config of your Views folder you'll find this:
<system.web>
<httpHandlers>
<add path="*" verb="*" type="System.Web.HttpNotFoundHandler"/>
</httpHandlers>
which means that any request to this folder will be handled with the HttpNotFoundHandler, thus you'll get the "not found" message.

And still, what is the magic of ASP.NET MVC Content folder?

I've just moved my resource files (javascript, css, images) from Content folder to custom Assets folder. And I've noticed a strange behavior - these files are not longer cached by browser and MvcMiniProfiler shows separate request for each resource located in Assets folder:
I know that Content folder isn't required by ASP.NET MVC convention, but why this happens? Is Content treated somehow especially by anyone (e.g. ASP.NET, IISExpress, etc.)? And how to force caching for other folders that contain static resources?
EDIT: Oh, it appears to be not an ASP.NET MVC odd behavior, but just the standard behavior of MvcMiniProfiler. Currently I'm checking that...
EDIT: Yea, there is no problem with ASP.NET MVC, it's just a default configuration of MvcMiniProfiler to ignore only these paths: "/mini-profiler-", "/content/", "/scripts/", "/favicon.ico". And these defaults can be easily extended:
MiniProfiler.Settings.IgnoredPaths = MiniProfiler.Settings.IgnoredPaths
.Concat(new [] { "/assets/" })
.ToArray();
Sometimes it's a good idea to read documentation before using something ;)
As you're indicating in your update, this appears to be a feature of MvcMiniProfiler:
/// <summary>
/// When <see cref="MiniProfiler.Start"/> is called, if the current request url contains any items in this property,
/// no profiler will be instantiated and no results will be displayed.
/// Default value is { "/mini-profiler-", "/content/", "/scripts/", "/favicon.ico" }.
/// </summary>
[DefaultValue(new string[] { "/mini-profiler-", "/content/", "/scripts/", "/favicon.ico" })]
public static string[] IgnoredPaths { get; set; }
Source.
Presumably, the images were never cached when you were serving them through Cassini, because Cassini is terrible at that (passing png files as application/octet-stream, for instance), but the issue was manually hidden from your view by MvcMiniProfiler.
This is a strange behavior. However, put the following code inside your web.config file which is under the root of your app:
<system.webServer>
<staticContent>
<clientCache cacheControlMode="UseMaxAge" cacheControlMaxAge="30.00:00:00" />
</staticContent>
</system.webServer>
This code appends the necessary response headers in order for browser caching to work. You can tweak the time of course. For further info please refer : http://www.iis.net/ConfigReference/system.webServer/staticContent/clientCache

MVCContrib portable areas not working from HtmlExtensions, MVC 3

I just implemented MVCContrib's Portable Area feature and it works fine. I can open it using:
http://localhost/projectname/portableAreaName, but this portable area is not working if i render it using the HtmlHelper extension method like this:
public static void RenderHtmlWidget(this HtmlHelper Html)
{
Html.RenderAction("Index", "HtmlWidget", new {area = "HtmlWidget"});
}
And calling the helper method in the view as such:
#using Project.Widgets.HtmlWidget;
#{Html.RenderHtmlWidget();}
I'm getting an error: The view 'Index' or its master was not found or no view engine supports the searched locations. In the possible location list there are no ~/areas/... defined.
But I can render my HtmlWidget successfully with this the same line of code in the view:
#{Html.RenderAction("Index", "HtmlWidget", new { area = "HtmlWidget" });}
What am I doing wrong and how should I use the HtmlHelper extensions correctly with the MVCContrib portable areas feature?
There are a few things that may be causing this.
In the calling/parent project where you use the helper method to invoke your portable area, do you have a Web.config file in the /Areas/ folder? If not, you must copy the Web.config found in the /Views/ folder of the same project, and simply place the new copy in the /Areas/ folder as well.
In the Registration class file in your portable area project, after you call MapRoute in the "RegisterArea" method, are you calling "RegisterAreaEmbeddedResources();"?
Is each view in your portable area project made to be an embedded resource as opposed to content? Select a View in the Solution Explorer and hit F4, "Build Action" should be set to "Embedded Resource", but it defaults to "Content"
You also need to make sure that both the Portable project and the consuming project reference the same version of MvcContrib, but that they also utilize the same version of ASP.NET MVC. If your area is referenced in multiple projects, each based off of a different version of MVC (not likely, but possible depending on the situation), your area must use whatever version of MVC the consuming project uses.
I'd also suggest using Phil Haack's .NET Routing Debugger - its a single DLL file that you reference in the consuming application and add a single line to your ApplicationStart() in your Global.asax.cs. This becomes incredibly helpful in determining if your portable area is being correctly registered with the base project - and helps you cut to the chase.

Exclude HttpModule from running for static content on IIS7

I have a problem with my Authentication HttpModule. The problem is that it obviously runs for every single request I get on my web server (IIS7). Because it also uses Session variable, it fails to work properly on CSS, JS files and similar.
I tried to use:
<add name="AuthModuleName" type="..." preCondition="managedHandler" />
but to no avail. It still runs on every request regardless of its extension or mime type. I should also add, there's a setting
<modules runAllManagedModulesForAllRequests="true">
that seemed suspicious to me and actually disabled preConditions on modules. But changing it to false, breaks the application in a completely different way and with a different exception (The SessionStateTempDataProvider requires SessionState to be enabled).
Can anybody help me how to force IIS7 to exclude my HttpModule when requests are made for static content files?
runAllManagedModulesForAllRequests attribute has to be set to false to actually configure any module the way you want. You will have to also correctly reconfigure Session and others as needed, but the main thing is handlers pipeline execution order that handles requests.
The answer was provided in one of my other questions:
Thanks to Peter that provided the answer that worked correctly.
I don't know about an IIS7 setting for that but you can do this.
The session object will be available only for non-static content :
void yourEventHandler(object sender, EventArgs e) {
HttpApplication app = (HttpApplication)sender;
if (app.Context.Session == null) {
return;
}
// then your code here...
}
This will ensure your code won't be run for files like CSS, JS etc. But keep in mind the session object will also not be ready before PostAcquireRequestState event. (For the order of the HttpApplication events, see this page.)
Edit : Also, it appears with ASP.NET Development Server (though I know you said IIS7 in your question), your HttpModule will still run even for static files.

ASP.net MVC [HandleError] not catching exceptions

In two different application, one a custom the other the sample MVC application you get with a new VS2008 MVC project, [HandleError] is not catching exceptions.
In the sample application I have:
[HandleError]
public class HomeController : Controller
{
public ActionResult Index()
{
ViewData["Message"] = "Welcome to ASP.NET MVC!";
throw new Exception();
return View();
}
public ActionResult About()
{
return View();
}
}
which is just the default controller with an exception being thrown for testing.
But it doesn't work. Instead of going to the default error.aspx page it shows the debug information in the browser.
The problem first cropped up in a custom application I'm working on which led me to test it with the sample application. Thinking it had something to do with changes I made in the custom application, I left the sample application completely unchanged with the exception (yuck) of the throw in the index method.
I'm stumped. What am I missing?
In Web.config, change customErrors:
<system.web>
<customErrors mode="On">
</customErrors>
If mode is either Off or RemoteOnly, then you will see the yellow screen of death instead of the custom error page. The reasoning is that developers usually want the more detailed information on the yellow screen of death.
Important: Be careful that your error page itself does not have an error on it!
If it does you'll end up with that ASP.NET custom error page and end up going round in circles and tearing your hair out. Just strip everything out of the page that could possibly cause an error and test it.
Also with respect to 'customErrors' being ON or OFF there are several contributing factors to whether or not the friendly error page (your Errors.aspx) page will be shown or not.
See this blog (except below)
HttpContext.IsCustomErrorEnabled - looks at three different sources
The web.config's <deployment> section's retail property. This is a
useful property to set when deploying
your application to a production
server. This overrides any other
settings for custom errors.
The web.config's <customErrors> section's mode property. This setting
indicates whether custom errors are
enabled at all, and if so whether they
are enabled only for remote requests.
The HttpRequest object's IsLocal property. If custom errors are enabled
only for remote requests, you need to
know whether the request is from a
remote computer.
The idea here is that you can have 'customErrors' turned OFF during development - when you do want to see the errors, and then enable it for production only.
This MSDN article discusses the attribute further.
Another reason for this problem may be ,
In Template MVC Application (generated by VS2008 / VS2008 Express) , Error.aspx (generated by VS) uses Master Page.
If Master Page access any ViewData it will throw null reference Exception , then the error.aspx won't be shown.
Use this Simple code as your Error.aspx , it will solve the problem, (along with CustomErrors=On )
<%# Page Language="C#" Inherits="System.Web.Mvc.ViewPage<System.Web.Mvc.HandleErrorInfo>" %>
<%= Model.Exception.Message %>
I have struggled with this as well and I believe I understand the problem now.
In short the requirements for having [HandleError] work as expected are:
You must enable custom errors in web.config AND you must also specify where your error view is in the <customErrors> tag.
Example:
<customErrors mode="On" defaultRedirect="Error" />
Leaving off the defaultRedirect="Error" part will instead yield a 500 error in the browser--NOT the ASP.NET error page (YSOD).
Also you do not have to be in Release mode. I tested this with a Debug build and it worked fine.
My environment was Visual Studio 2010 using .NET 4 and the standard, "ASP.NET MVC 2 Web Application" project template.
What confused me was the MSDN documentation for the HandleErrorAttribute Class. It doesn't explicitly say you must turn on custom errors in web.config. And I assumed all I needed was the [Handle Error] attribute.
There is some silly situation which once happened with me, so might be helpfull for someone.
Be sure that you've added <customErrors mode="On" /> to the correct web.config file.
Sometimes (especially, when you work with something like Resharper, and open your files with typing their name, but not via Solution Explorer), you can simply open a web.config either from Views folder or even from another project.
Watch out: in my case I was trying to get the HandleError attribute to catch an exception thrown inside the Controllers constructor! Of course it won't catch it. The HandleError attribute only catches exceptions thrown inside Controller actions. It's right there in the MSDN page (should've paid more attention to that):
Represents an attribute that is used to handle an exception that is
thrown by an action method.
Another thing that was happening is that the Controller's OnException(ExceptionContext exceptionContext) overridden method was never being called. Again: of course it would not be called since I was throwing an exception inside the Controller's constructor.
I spent 1 hour trying to figure this out. :o) Hope it helps the next soul...
As a hint: remember that the HandleError attribute only catches 500 errors. For the other ones you should declare the <customErrors> section in Web.config:
<customErrors mode="On">
<error statusCode="403" redirect="~/403" />
<error statusCode="404" redirect="~/404" />
</customErrors>

Resources