Website on primary domain + secure MVC web app on subdomain - asp.net-mvc

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).

Related

Bypass ASP MVC bundle requests from MVC module

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).

Web Api 2 global route prefix for route attributes?

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)

How to disable or reprioritize IIS DirectoryListingModule under MVC module?

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??? */;
}

How can I accept email address as a route value?

How can I have this simple Route:
http://domain.com/Calendar/Unsubscribe/my#email.com
I have a route that looks like:
routes.MapRoute(
"Unsubscribe",
"Calendar/Unsubscribe/{subscriber}",
new {
controller = "Calendar",
action = "Unsubscribe",
subscriber = "" }
);
and my action is:
public ActionResult Unsubscribe(string subscriber)
{
...
}
Without any parameters, like http://domain.com/Calendar/Unsubscribe/ works fine, but soon I add the email, I get a 404 page :(
Is there any trick I have to do?
Thank you
Try adding a trailing slash to the url http://domain.com/Calendar/Unsubscribe/my#email.com/ without changing the routing rules.
If you still want to avoid adding the trailing slash and you have URL Rewrite available, you could add a rewrite rule into the web.config
<system.webServer>
<rewrite>
<rules>
<rule name="Fix Unsubscribe emails route" stopProcessing="true">
<match url="^(Calendar/Unsubscribe/.*#.*)$" />
<action type="Rewrite" url="{R:1}/" />
</rule>
</rules>
</rewrite>
</system.webServer>
You could also write a better regular expression than the one I provided for the sake of readability.
You could also try to reorder your route params like /Calendar/my#email.com/Unsubscribe so that the e-mail is not the last param.
I think this should do it.
routes.MapRoute(
"Unsubscribe",
"Calendar/Unsubscribe/{subscriber}",
new {
controller = "Calendar",
action = "Unsubscribe"
}
new { subscriber = #"\w+([-+.]\w+)*#\w+([-.]\w+)*\.\w+([-.]\w+)*" }
);
I tried it in default home controllor and working with no error
_http://localhost:64718/home/index/a.b#email.com
Welcome to ASP.NET MVC! a.b#email.com
public ActionResult Index(string id)
{
ViewModel.Message = "Welcome to ASP.NET MVC! " + id;
return View();
}
No changes in defaultroute-MVC.
do you have any other routes defined before Unsubscribe which will match same route
Try removing the default empty string for subscriber.
The # symbol is a reserved character in URLs:
http://en.wikipedia.org/wiki/Percent-encoding
Try encoding it; Thusly, your new url would be:
http://domain.com/Calendar/Unsubscribe/my%40email.com
you don't need to add new rote! I tried it in Asp.Net Mvc4 application without any new routes and it worked. you can check email in Unsubscribe Method. I think it's better to do that.
good luck!

How to use a SubDomain as Route Value in MVC2

first:
I did read and tried to implemented this and this and that, but I failed completely :(
my route is like:
routes.MapRoute(
"DefaultRoute",
"{calurl}/{controller}/{action}/{id}",
new { calurl = "none", controller = "Subscriber", action = "Index", id = UrlParameter.Optional }
);
and I'm trying to use as
"{calurl}.domain.com",
"{controller}/{action}/{id}"
so the routed value calurl would always come from the subdomain.
and I could have links like:
http://demo.domain.com/Subscriber/Register
as of today I have
http://domain.com/demo/Subscriber/Register
What I have tried
I tried to create my own CustomRoute using the example of the links above (all 3, one at a time), and I end up always screwing everything.
and I'm keep thinking that it's to much code just to change RouteValue["calurl"] to the subdomain.
What/How can I do this?
I am not sure if Routing extends to the actual domain name, as the domain or sub-domain should'nt have any effect on the working of a site/application.
I would suggest building your own sub-domain detection on each request. This keeps Routing and the Sub-domain detection separate and will help with testing etc..
This may help:
public static string GetSubDomain()
{
string subDomain = String.Empty;
if (HttpContext.Current.Request.Url.HostNameType == UriHostNameType.Dns)
{
subDomain = Regex.Replace(HttpContext.Current.Request.Url.Host, "((.*)(\\..*){2})|(.*)", "$2").Trim().ToLower();
}
if (subDomain == String.Empty)
{
subDomain = HttpContext.Current.Request.Headers["Host"].Split('.')[0];
}
return subDomain.Trim().ToLower();
}
If you have IIS7, why not use a URL Rewrite rule?
This would probably be better than doing a hack job with your routes, and IIS would do what it does best.
Probably something like this:
<rule name="rewriteSubdomains" stopProcessing="true">
<match url="(.*).domain.com/(.*)" />
<action type="Rewrite" url="domain.com/{R:1}/{R:2}" />
</rule>
This way, your route will handle the subdomain correctly, as it comes into the app differently.

Resources