I have a multiclient system whereby different clients have different brand colours.
These colours are stored in the DB and referenced throughout my LESS files as #color{1-3}.
We used to maintain a colors.less file with a reference to all places where these colours featured. In a client-neutral situation we would just render the normal bundle with our brand colours but within a client area we would inject a stored CSS file generated when the client colours changed in the DB.
This worked fine but maintaining the colours.less file was becoming a bit unwieldy so what I'd like to do is render the css "real time" so that no css file needs to be generated manually. Obviously I can't do this every request because it would hammer the server with a fairly intensive css generation every page load.
My question is how can i use bundling or some other form of caching to generate this css on the fly (i.e without actually storing the css files) without hammering the server?
you can implement a custom http handler:
public class CustomHandler : IHttpHandler
{
#region IHttpHandler Members
public bool IsReusable
{
// Return false in case your Managed Handler cannot be reused for another request.
// Usually this would be false in case you have some state information preserved per request.
get { return true; }
}
public void ProcessRequest(HttpContext context)
{
var sb = new StringBuilder();
// read values from db and cache them with HttpContext.Current.Cache
sb.Append(".black : { color: 'black'; }");
context.Response.Clear();
context.Response.ContentType = "text/css";
context.Response.Write(sb.ToString());
}
#endregion
}
web.config:
<system.webServer>
<handlers>
<add name="ColorsHandler" verb="GET" path="colors.axd" type="WebApplication3.Infrastructure.CustomHandler, WebApplication3, Version=1.0.0.0, Culture=neutral" />
</handlers>
</system.webServer>
the only limitation is that you cannot reference this handler as MVC Bundle because MVC bundle can handle only static files. Thus you have to reference it as stylesheet:
<link rel="stylesheet" href="~/colors.axd" type="text/css">
Related
I have an ASP.NET MVC controller action like the following:
[HttpGet]
[DonutOutputCache(CacheProfile = "banner")]
public async Task<ActionResult> Banner(string name, string size = "lg")
{
// snipped - work out which banner to show today.
return File(thePathToSomeBannerImageFile, "image/jpeg");
}
And the configuration file looks like:
<caching>
<outputCacheSettings>
<outputCacheProfiles>
...
<add name="banner" duration="31536000" varyByParam="*" />
...
</outputCacheProfiles>
</outputCacheSettings>
</caching>
Since adding DonutOutputCache the image that I used to render to my browser now won't load. I can see in the Fiddler trace that a largish binary file is getting to the browser, but I can't seem to get it to render.
Removing the DonutOutputCache makes the image appear again.
I've tried clearing the cache etc. Any ideas?
Ah, just figured it out from this link: Doesn't work w/ Files?.
Basically DonutOutputCache uses ContentResult internally which works only with text-based content. Since I'm returning a binary response in a FileResult it messes stuff up. Fortunately it works side-by-side with the built in OutputCache, so I could modify my action as follows and it works as expected. I really only needed DonutOutputCache for other bits of my site that are text-based. I tripped myself up trying to just have one caching attribute to rule them all.
[HttpGet]
[OutputCache(CacheProfile = "banner")]
public async Task<ActionResult> Banner(string name, string size = "lg")
{
}
In my project we change one image depending on domain which was requested. Of course we can serve different files (css, js, html), but much more easy is to handle this one file and serve user correct one (let's call it logo.ico)
In web.config we set <modules runAllManagedModulesForAllRequests="false"> which causes that that IIS serve all static files for us.
Is there a way to add exception for `logo.ico' file?
To make example clear: when browser request http://mydomian.com/logo.ico I would like to run custom handler. For every other file I would like to use IIS native solution (which means <modules runAllManagedModulesForAllRequests="false">)
There is no way to add exception just for one file, but you could serve that image with ashx handler, for example url should look like (add your own logic to select image)
http://mydomian.com/ServeImage.ashx?image=logo.ico
And then just serve icon for that domain:
public class ServeImage : IHttpHandler
{
public void ProcessRequest(HttpContext context)
{
context.Response.ContentType = "image/x-icon";
using (var fs = new FileStream(context.Server.MapPath("~/App_Data/logo.ico"), FileMode.Open))
{
fs.CopyTo(context.Response.OutputStream);
}
}
public bool IsReusable
{
get
{
return false;
}
}
}
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 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??? */;
}
For my MVC project (Image Server Application), I cannot do caching using imageresizer. I can access my images like this and the image source could be either FileSystem/Database (Dependency injeciton) :
localhost/images/123.jpg?width=500
localhost/images/123?width=500
I have an MVC 3 project with routes like
routes.RouteExistingFiles = true;
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.IgnoreRoute("favicon.ico");
routes.MapRoute(
"ImagesWithExtension", // Route name
"images/{imageName}.{extension}/", // URL with parameters
new { controller = "Home", action = "ViewImageWithExtension", imageName = "", extension = "", id = UrlParameter.Optional } // Parameter defaults
);
routes.MapRoute(
"Images", // Route name
"images/{imageName}/", // URL with parameters
new { controller = "Home", action = "ViewImage", imageName = "", id = UrlParameter.Optional } // Parameter defaults
);
I have two controllers to deal with images
public ActionResult ViewImageWithExtension(string imageName, string extension) {}
public ActionResult ViewImage(string imageName) {}
The caching is done when the URL is like :
localhost/images/123.jpg?width=500 and the image source is FileSystem
localhost/images/123?width=500 Cache not working image source is Filesystem
localhost/images/123.jpg?width=500 Cache not working, image source DB
localhost/images/123?width=500 Cache not working , image source DB
My web config is like this:
<configSections>
<section name="resizer" type="ImageResizer.ResizerSection" /> </configSections>
<resizer>
<!-- Unless you (a) use Integrated mode, or (b) map all reqeusts to ASP.NET,
you'll need to add .ashx to your image URLs: image.jpg.ashx?width=200&height=20
Optional - this is the default setting -->
<diagnostics enableFor="AllHosts" />
<pipeline fakeExtensions=".ashx" />
<DiskCache dir="~/MyCachedImages" autoClean="false" hashModifiedDate="true" enabled="true" subfolders="32" cacheAccessTimeout="15000" asyncWrites="true" asyncBufferSize="10485760" />
<cleanupStrategy startupDelay="00:05" minDelay="00:00:20" maxDelay="00:05" optimalWorkSegmentLength="00:00:04" targetItemsPerFolder="400" maximumItemsPerFolder="1000" avoidRemovalIfCreatedWithin="24:00" avoidRemovalIfUsedWithin="4.00:00" prohibitRemovalIfUsedWithin="00:05" prohibitRemovalIfCreatedWithin="00:10" />
<plugins>
<add name="DiskCache" />
</plugins> </resizer>
Am I doing something wrong or Imageresizer doesnt support this scenario ? If not any good plugin to use disk based image cahce ?
Thanks in advance.
As I explained in the other public forums which you simultaneously posted this question, the ImageResizer supports dependency injection from the ground up. You're trying to wrap dependency injection with more dependency injection, but backwards.
A) ASP.NET MVC 3 and 4 prevent efficient disk caching, by design. You need to work with the ImageResizer HttpModule, not against it, to get good performance. That means using the URL API, not the Managed API wrapped by your own MVC ActionResults. Listen to my podcast with Scott Hanselman for more info.
B) SqlReader, S3Reader, MongoReader, VirtualFolder, and AzureReader all support dynamic injection, and can (with a tiny bit of configuration), all use the same path syntax. The ImageResizer is designed to allow easy migration between data stores.
C) You can use the Config.Current.Pipeline.Rewrite event to make the URL API use any syntax you want. This is much more flexible than MVC routes (and less buggy).
D) If you want to add another layer of dependency injection, implement IPlugin, and dynamically pick the appropriate data store and configure it from the Install method.