I have HttpModule in place for URL encryption; I noticed this module is also intercepting MVC bundle requests; even if I am using following settings:
<modules runAllManagedModulesForAllRequests="false">
<add ..preCondition="managedHandler" />
</modules>
Is there any way to bypass the interception of MVC bundle requests from HttpModule?
so for ignoring certain route in httpmodule you can use Application_BeginRequest or Application_EndRequest , do as below in example which ignores aspx pages or check for the path you want to ignore
Example :
private void Application_BeginRequest(Object source, EventArgs e)
{
HttpApplication application = (HttpApplication)source;
HttpContext context = application.Context;
string filePath = context.Request.FilePath;
string fileExtension = VirtualPathUtility.GetExtension(filePath);
if (fileExtension.Equals(".aspx"))
{
return;
}
}
But if your functionality is related to some modules of application I suggest make use of Filters(MVC sepcific and for modules) rather than HttpModules(Specific to full application).
Related
I have the following code in my Global.asax:
void Application_EndRequest(object sender, EventArgs e)
{
HttpApplication application = sender as HttpApplication;
HttpContext context = application.Context;
string path = context.Request.Path;
string contentType = context.Response.ContentType;
System.Diagnostics.Debug.WriteLine("-----------------------------------");
System.Diagnostics.Debug.WriteLine("Path: " + path);
System.Diagnostics.Debug.WriteLine("ContentType:" + contentType);
}
I have a Help folder at the root of the site (~/Help) that contains static .htm files. I notice that not all of these files are being run through EndRequest. Sometimes I see assets in the page being logged (e.g. .js files) but not the htm file itself. Sometimes they do get logged.
Why don't all of these files run through EndRequest and how can I ensure that they do?
In the end my configuration is like this:
AppPool pipeline: Integrated
RouteExistingFiles: false (default)
runAllManagedModulesForAllRequests: true
All of this was how it was when I wrote up this question. The thing I did different was to go into my web.config and manually add a handler under <httpHandlers>:
<add verb="GET,HEAD,POST,DEBUG" path="*.htm" type="System.Web.UI.PageHandlerFactory" />
One thing that was throwing me off was that once the file had come down the browser was caching it and not re-requesting it (until I cleared the cache).
The problem
I'm trying to achieve the following:
Host a static website on http://www.somedomain.com.
Host an ASP.NET MVC app on https://secure.somedomain.com
(Note: This domain/subdomain is for illustrative purposes only.)
I've obtained an SSL/TLS certificate from my host, WinHost, and I'm applying it only to the subdomain (secure.somedomain.com), not the primary domain (somedomain.com).
When I publish the MVC app, the files are actually stored in the directory somedomain.com/secure.
Getting this all to work has proven much more difficult then I thought it would be.
What I've tried so far
I've found a number of questions about how to have different subdomains for different purposes (e.g., one subdomain per language, one subdomain per company, etc.), but my needs are much simpler--I just want my entire MVC app to be hosted from a single subdomain.
I've attempted two basic approaches so far (both independently and in combination):
Use IIS Manager to set up URL rewrites that basically translate requests for secure.somedomain.com -> somedomain.com/secure.
Write custom routes and/or constraints within my MVC app and add them to the RouteCollection in my Global.asax.cs file.
I've had some luck with Approach #1. I eventually managed to come up with a couple rules that would (1) not interfere with the website on the primary domain, (2) block any requests for somedomain/secure (because the security certificate is not in effect for the primary domain), and (3) serve up the correct page upon requests for secure.somedomain.com.
The major failure of this approach (at least in isolation) is that static content no longer gets served, and requests by unauthenticated users result in incorrect URLs, such as "https://secure.somedomain.dom/secure/somecontroller/someaction/?ReturnUrl=%2fsomecontroller" (note the extra "secure").
For the record, here are my URL rewrites:
<system.webServer>
<rewrite>
<rules>
<rule name="subdomain" stopProcessing="true">
<match url="(.*)" />
<conditions>
<add input="{HTTP_HOST}" pattern="^(secure.)somedomain.com$" />
</conditions>
<action type="Rewrite" url="\secure\{R:0}" />
</rule>
<rule name="subfolder" stopProcessing="true">
<match url=".*" />
<conditions>
<add input="{URL}" matchType="Pattern" pattern="^(http://|https://)?(www)?[^/]*/secure" ignoreCase="true" negate="false" />
</conditions>
<action type="CustomResponse" statusCode="403" statusReason="Forbidden: Access is denied." statusDescription="You do not have permission to view this directory or page using the credentials that you supplied." />
</rule>
</rules>
</rewrite>
</system.webServer>
As for Approach #2, I honestly can't say that anything I've tried has made much of a difference (whether in combination with the URL rewrites or alone).
First, I tried to write the following custom route, which I basically stole from https://stackoverflow.com/a/15287579/129164:
public class SubdomainRoute : Route
{
private const string _subdomain = "secure";
public SubdomainRoute(string url) : base(url, new MvcRouteHandler()) { }
public override RouteData GetRouteData(HttpContextBase httpContext)
{
var routeData = base.GetRouteData(httpContext);
if (routeData == null) return null; // Only look at the subdomain if this route matches in the first place.
// A subdomain specified as a query parameter takes precedence over the hostname.
var subdomain = httpContext.Request.Params[_subdomain];
if (subdomain == null)
{
var host = httpContext.Request.Headers["Host"];
var index = host.IndexOf('.');
if (index >= 0) subdomain = host.Substring(0, index);
}
if (subdomain != null) routeData.Values[_subdomain] = subdomain;
return routeData;
}
public override VirtualPathData GetVirtualPath(RequestContext requestContext, RouteValueDictionary values)
{
var subdomainParam = requestContext.HttpContext.Request.Params[_subdomain];
if (subdomainParam != null) values[_subdomain] = subdomainParam;
return base.GetVirtualPath(requestContext, values);
}
}
I attempted to hook it up like this in Global.asax.cs:
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.Add(
"Default",
new SubdomainRoute("{controller}/{action}/{id}")
{
Defaults = new RouteValueDictionary(
new { controller = MVC.Home.Name, action = MVC.Home.ActionNames.Index, id = UrlParameter.Optional }),
Constraints = new RouteValueDictionary(
new { controller = #"[^\.]*" })
});
}
(I basically replaced routes.MapRoute() with routes.Add().)
I also attempted to create a custom route constraint instead of a custom route, as suggested by https://stackoverflow.com/a/15234839/129164. My class looked like this:
public class SubdomainRouteConstraint : IRouteConstraint
{
private readonly string _subdomain;
public SubdomainRouteConstraint(string subdomain)
{
_subdomain = subdomain;
}
public bool Match(
HttpContextBase httpContext,
Route route,
string parameterName,
RouteValueDictionary values,
RouteDirection routeDirection)
{
return httpContext.Request.Url != null && httpContext.Request.Url.Host.StartsWith(_subdomain);
}
}
And I updated my RegisterRoutes method to this:
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute(
"Default" /* Route name */,
"{controller}/{action}/{id}" /* URL with parameters */,
new { controller = MVC.Home.Name, action = MVC.Home.ActionNames.Index, id = UrlParameter.Optional } /* Parameter defaults */,
new { controller = #"[^\.]*", subdomain = new SubdomainRouteConstraint("secure") } /* Parameter constraints */);
}
Again, I saw no difference when I tried this.
Some of the other answers I came across suggested writing extension methods to basically modify Html.ActionLink and Url.Content to fix the URLs as necessary, but I'm really resistant to this approach because it would involve a lot of refactoring to hook it up (and given my luck so far, I'm not confident it will work).
Anyway, if you are still with me, I could really use some advice on how to approach setting up my secure subdomain (hopefully) without having to make major sweeping changes to my MVC app.
Also, if you see any glaring mistakes in the solutions I've attempted so far, or you have some thoughts on specific modifications or combinations I should try, please let me know.
I may not fully understand your setup/requirements but if you have two separate domains for two entities with separate purposes, why don't you use two web sites rather than a single one.
Just set the host header in the bindings and have the root of the secure site pointing to the secure folder. If you need some static files from the static site, just bring them in via a virtual directory. No need for URL rewrites or routing.
If you need to store the secure folder under the static site but don't want users to see it there, just hide it:
<system.webServer>
<security>
<requestFiltering>
<hiddenSegments>
<add segment="secure" />
</hiddenSegments>
</requestFiltering>
</security>
</system.webServer>
A similar issue here- somedomain.com and abc.subdomain.com. (both in ASP.Net 4.0)
Static html files in somedomain.com
Deployed a simple aspx file into the abc.subdomain.com. - Works fine.
Deployed a simple mvc3 application into the abc.subdomain.com. - Not working. Showing the message Forbidden - you dont have permission to access / on this server. (error 403).
No changes made to global.asax and webconfig.
Deployment mode - publish to a local folder and ftp upload. (all dependencies added).
I'd like to expose a company's api by two ways:
api.company.com (pure WebApi web site)
company.com/api (add WebApi to existing MVC5 company site)
So, I placed models/controllers in a separate assembly and reference it from both web sites.
Also, I use route attributes:
[RoutePrefix("products")]
public class ProductsController : ApiController
Now, the controller above can be accessed by:
api.company.com/products which is good
company.com/products which I'd like to change to company.com/api/products
Is there a way to keep using route attributes and setup MVC project so it adds "api" for all routes?
So this is probably not the only way you could do it, but this is how I would do it:
Create your own Attribute that inherits from RoutePrefixAttribute
Override the Prefix property and add some logic in there to prepend "api" to the prefix if running on the desired server.
Based on a setting in your web.config, prepend to the route or not.
public class CustomRoutePrefixAttribute : RoutePrefixAttribute
{
public CustomRoutePrefixAttribute(string prefix) : base(prefix)
{
}
public override string Prefix
{
get
{
if (Configuration.PrependApi)
{
return "api/" + base.Prefix;
}
return base.Prefix;
}
}
}
EDIT
(The below option is no longer supported as of Web API 2.2)
Alternatively you could also specify more than one route prefix:
[RoutePrefix("api/products")]
[RoutePrefix("products")]
public class ProductsController : ApiController
You can use Map on IAppBuilder
So Startup class will looks something like this
class Startup
{
public void Configuration(IAppBuilder app)
{
app.Map("/api", map =>
{
HttpConfiguration config = new HttpConfiguration();
config.MapHttpAttributeRoutes();
map.UseWebApi(config);
});
}
}
Another option would be to forward traffic from one site to your Web API. Are you set on hosting two instances? If not, you could host only the one instance under say api.company.com/products. On your MVC company site implement an HttpHandler to redirect all traffic matching /api/* to api.company.com:
a. Create the handler in your MVC app:
public class WebApiHandler : IHttpHandler
{
public void ProcessRequest(HttpContext context)
{
string url = "api.company.com" + context.Request.RawUrl.Replace("/api","");
//Create new request with context.Request's headers and content
//Write the new response headers and content to context.Response
}
}
b. Register the handler in your web.config:
<configuration>
<system.web>
<httpHandlers>
<add verb="*" path="api/*" type="Name.Space.WebApiHandler" validate="false" />
</httpHandlers>
</system.web>
</configuration>
c. Enable CORS in your Web API if you haven't done so already.
You can just implement your api service as www.mycompany.com/api.
Then use UrlRewrite to map api.mycompany.com to www.mycompany.com/api
We even support this method of UrlRewrite in link generation, so if you generate links from the api.mycompany.com, your links will point to api.mycompany.com/controller/id.
Note that this is the only form of URL rewrite that works correctly for MVC link generation (api.xxx.yyy -> www.xxx.yyy/api)
I use an MVC folder structure where the URL routes happen to match the directory names, eg.:
<proj>\My\Cool\Thing\ThingController.cs
Needs to be accessible by this url:
http://blahblah/My/Cool/Thing
I have the MVC routing working but unfortunately when relying on default {action} & {id}, IIS Express is routing the request to DirectoryListingModule instead, since it directly matches a folder name. Directory listing is disabled of course so instead I get:
The Web server is configured to not list the contents of this directory.
Module DirectoryListingModule
Notification ExecuteRequestHandler
Handler StaticFile
To fix this I have already tried:
1. runAllManagedModulesForAllRequests = true
<system.webServer>
<modules runAllManagedModulesForAllRequests="true" >
//Makes no difference
2. Removing module
<system.webServer>
<modules runAllManagedModulesForAllRequests="true" >
<remove name="DirectoryListingModule"/>
// Won't let me as module is locked in IIS
</modules>
</system.webServer>
3. Removing lock & module
// applicationhost.config
<add name="DirectoryListingModule" lockItem="false" />
// web.config
<remove name="DirectoryListingModule"/>
// Causes startup error"Handler "StaticFile" has a bad module "DirectoryListingModule" in its module list"
4. Removing lock & removing/readding module (to change order) - makes no difference
// web.config
<remove name="DirectoryListingModule"/>
<add name="DirectoryListingModule"/>
Tearing my hair out. How can I get IIS to route this to my MVC app instead of DirectoryListingModue?? Preferably a solution in web.config so we don't need to reconfigure IIS in production.
(One workaround is to keep my folder structure but store it all under /Areas/... instead, just to break the match between folder path & url. This is a terrible hack & last resort.)
edit to add route mapping
I am creating custom routes relative to each controller's namespaces (namespaces always match folders). Note that everything is put under the "Modules" namespace / folder currently just to avoid the problem described above.
private static void RegisterAllControllers(RouteCollection routes)
{
const string controllerSuffix = "Controller";
const string namespacePrefix = "My.Cool.Websire.UI.Modules.";
var controllerTypes = Assembly.GetExecutingAssembly().GetTypes().Where(x => x.IsSubclassOf(typeof(Controller))).ToList();
foreach (var controllerType in controllerTypes)
{
// Turn My.Cool.Website.UI.Modules.X.Y.Z.Abc.AbcController into a route for url /X/Y/Z/Abc/{action}/{id}
var fullNamespace = controllerType.Namespace ?? "";
var relativeNamespace = fullNamespace.Substring(namespacePrefix.Length, fullNamespace.Length - namespacePrefix.Length);
var controllerName =
controllerType.Name.EndsWith(controllerSuffix)
? controllerType.Name.Substring(0, controllerType.Name.Length - controllerSuffix.Length)
: controllerType.Name;
var url = relativeNamespace.Replace(".", "/") + "/{action}/{id}";
var routeName = "Dedicated " + controllerName + " route";
routes.MapRoute(routeName, url, new { controller = controllerName, action = "Index", id = UrlParameter.Optional });
}
}
My solution at this stage is to place the MVC contents of the WebUI project under a /Modules/ folder:
My.Cool.Site.WebUI/Modules/Something/Blah/BlahController
My.Cool.Site.WebUI/Modules/Something/Blah/Views/...
My.Cool.Site.WebUI/Modules/Something/Blah/PartialViews/...
Then using the route code posted I can access this via url:
http://.../Something/Blah/[action]
Because the files are under /Modules/ folder, this breaks the match between the URL and the folder path which gets around my problem.
Not a great solution but does the job.
I think if you want to separate your controllers in folders, so they are not under the "controllers", you should use "areas" feature that MVC provides. It is designed for this purpose.
I've noticed that if I have a custom http module, pointed to from web.config like
<modules>
<remove name="WebDAVModule" />
<add name="CustomHttpModule" type="MyCompany.Types.CustomHttpModule" preCondition="managedHandler" />
</modules>
Then I can get code to run before DirectoryListingModule hijacks the process, but I haven't found out what to do about it when I've detected it's about to hit a physical folder.
using System.IO;
using System.Web;
using System.Linq;
using System.Linq.Expressions;
namespace MyCompany.Types
{
public class CustomHttpModule : IHttpModule
{
public void OnAcquireRequestState(object sender, EventArgs ea)
{
List<string> physicalFolders = Directory.EnumerateDirectories(AppContext.BaseDirectory).Select(f => f.Substring(1 + f.LastIndexOf('\\'))).ToList();
string projectBase = /* get from web.config */.TrimStart('/');
string possiblePhysicalFolder = application.Request.Url.AbsolutePath.TrimStart('/').Replace(projectBase, "").TrimStart('/');
if (physicalFolders.Exists(f => possiblePhysicalFolder.StartsWith(f)))
/* what to do??? */;
}
I'm using castle windsor 3.1.0.0 for dependency injection in my MVC 3.0 application.
My container is setup to provide controllers like this:
container.Register(Classes.FromThisAssembly().BasedOn<IController>().LifestylePerWebRequest());
This seems to be working as I see a new controller instance created for every request. However according to the documenation: http://docs.castleproject.org/Windsor.LifeStyles.ashx, I must also place this in my web.config:
<httpModules>
<add name="PerRequestLifestyle" type="Castle.MicroKernel.Lifestyle.PerWebRequestLifestyleModule, Castle.Windsor"/>
</httpModules>
which I don't have. What is the behavior of Castle Windsor if this module is missing? (The documentation says that In order to function properly per web request you must have this in your web config).
As far as I understand, the PerWebRequestLifestyle requires an IHttpModule so that it can piggy-back off the init method and the HttpApplication events such as BeginRequest.
The reason why everything seems to be working is because the module has been initialised and so the PerWebRequestLifestyle is functioning normally.
But why is that the case if you didn't include the registration module? I suspect that it is a legacy instruction and that the container will attempt a registration on its own, but this isn't documented explicitly.
If we take a peek inside CastleWindsor we find something called Castle.MicroKernel.Lifestyle.PerWebRequestLifestyleModuleRegistration. It has this method:
public static void Run()
{
Type type = Type.GetType("Microsoft.Web.Infrastructure.DynamicModuleHelper.DynamicModuleUtility, Microsoft.Web.Infrastructure, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35", false);
if (type == null)
{
return;
}
MethodInfo method = type.GetMethod("RegisterModule", BindingFlags.Static | BindingFlags.Public);
if (method == null)
{
return;
}
object[] objArray = new object[] { typeof(PerWebRequestLifestyleModule) };
method.Invoke(null, objArray);
}
What is DynamicModuleUtility? A quick search reveals a page written by K. Scott Allen called DynamicModuleUtility.
The DynamicModuleUtility will let you install an HTTP module into the ASP.NET pipeline without making any changes to web.config file.
This is only my speculation as to what's going on. You'd have to ask the creators of Castle Windsor for details on exactly how things are working.