Could not override http cache headers in IIS using PreSendRequestHeaders() - asp.net

History:
Due to security considerations, our organization wants to disable caching by adding HTTP Headers to IIS.
Expires: -1
Pragma: no-cache
Cache Control: No-cache, No-store
Adding these headers cause MIME "application/vnd.ms-excel" response types to fail over SSL in IE6. Microsoft ackowledges this is as a bug (http://support.microsoft.com/kb/323308) and their solution also works. However, this solution has to pushed as a patch throughout the entire organization and that faces resistance from higher management.
Problem:
Meanwhile, we are trying to find alternatives by overriding IIS set HTTP headers for pages that have MIME type "application/vnd.ms-excel" using HTTPModules on PreSendRequestHeaders() function
//this is just a sample code
public void Init(HttpApplication context)
{
context.PreSendRequestHeaders += new EventHandler(context_PreSendRequestHeaders);
}
protected void context_PreSendRequestHeaders(object sender, EventArgs e)
{
HttpApplication application = (HttpApplication)sender;
if(application.Response.ContentType == "application/vnd.ms-excel; name=DataExport.xls")
{
application.Response.ClearHeaders();
application.Response.ContentType = "application/vnd.ms-excel; name=DataExport.xls";
application.Response.AddHeader("Content-Transfer", "Encoding: base64");
application.Response.AddHeader("Content-Disposition", "attachment;filename=DataExport.xls");
application.Response.AddHeader("cache-control","private");
}
}
Even after clearing headers using ClearHeaders(), IIS still appends Cache Headers before sending the response.
Questions:
Is this approach of using ClearHeaders() in PreSendRequestHeaders() function wrong?
Are they any alternatives to override cache headers(Expires,Pragma,cache-control) using libraries available in ASP.NET 1.1?
Misc:
We are using
Browser : IE6 SP 3
Server : IIS 6
Platform : .NET 1.1

This becomes easier with IIS 7.5+ using using the URL Rewrite extention and adding an outbound rule to strip off the "no-store" value in the Cache-Control header, and the Pragma header. This rule set would do the trick:
<outboundRules>
<rule name="Always Remove Pragma Header">
<match serverVariable="RESPONSE_Pragma" pattern="(.*)" />
<action type="Rewrite" value="" />
</rule>
<rule name="Remove No-Store for Attachments">
<conditions>
<add input="{RESPONSE_Content-Disposition}" pattern="attachment" />
</conditions>
<match serverVariable="RESPONSE_Cache-Control" pattern="no-store" />
<action type="Rewrite" value="max-age=0" />
</rule>
</outboundRules>

Please see:
Cache-control: no-store, must-revalidate not sent to client browser in IIS7 + ASP.NET MVC
You must use the following sequence of calls inside your PreSendRequestHeaders handler to correctly set the no-cache headers, otherwise the Cache-Control header gets overwritten later:
Response.Cache.SetCacheability(HttpCacheability.NoCache);
Response.Cache.AppendCacheExtension("no-store, must-revalidate");
Response.AppendHeader("Pragma", "no-cache");
Response.AppendHeader("Expires", "0");

Related

Image Headers - Resource should use cache busting but URL does not match configured patterns

According to Edge, some images have "issues" on them and the message I get is above and below is the headers for one of the images
accept-ranges: bytes
cache-control: immutable,max-age=31536000
content-length: 4374
content-type: image/webp
date: Thu, 18 Feb 2021 20:37:58 GMT
last-modified: Sun, 15 Nov 2020 12:32:57 GMT
strict-transport-security: max-age=31536000
x-content-type-options: nosniff
x-sourcefiles: =?UTF-8?B?543548399583849823 (Ok, I changed this setting)
The link that Edge points to is :-
https://webhint.io/docs/user-guide/hints/hint-http-cache/?source=devtools
I did what they recommended for IIS :-
<location path="mystatic">
<system.webServer>
<staticContent>
<clientCache cacheControlMode="UseMaxAge" cacheControlMaxAge="365.00:00:00" cacheControlCustom="immutable" />
</staticContent>
</system.webServer>
</location>
I still get the error, I have no idea what the error/warning message means or how to fix, can anyone enlighten me?
You could try to follow the belo steps:
<staticcontent>
<clientcache cachecontrolmode="UseMaxAge" cachecontrolmaxage="365.00:00:00" />
</staticcontent>
This code allows browsers to automatically cache all static resources for 365 days.
use class that adds a fingerprint, or timestamp, to the URL of the static file.
using System;
using System.IO;
using System.Web;
using System.Web.Caching;
using System.Web.Hosting;
public class Fingerprint
{
public static string Tag(string rootRelativePath)
{
if (HttpRuntime.Cache[rootRelativePath] == null)
{
string absolute = HostingEnvironment.MapPath("~" + rootRelativePath);
DateTime date = File.GetLastWriteTime(absolute);
int index = rootRelativePath.LastIndexOf('/');
string result = rootRelativePath.Insert(index, "/v-" + date.Ticks);
HttpRuntime.Cache.Insert(rootRelativePath, result, new CacheDependency(absolute));
}
return HttpRuntime.Cache[rootRelativePath] as string;
}
}
modify the references to the static files.
<link rel="stylesheet" href="<%=Fingerprint.Tag(" />content/site.css") %>" />
now it will look like below:
<link rel="stylesheet" href="/content/v-633332386847483941/site.css" />
Since the URL now has a reference to a non-existing folder, we need to make the webserver pretend it exists. We do that with URL rewriting.
<rewrite>
<rules>
<rule name="fingerprint">
<match url="([\S]+)(/v-[0-9]+/)([\S]+)" />
<action type="Rewrite" url="{R:1}/{R:3}" />
</rule>
</rules>
</rewrite>

Access-Control-Allow-Origin header completely ignored by FireFox

I set up my website (running IIS8.5) to send the response header for CORS to a subdomain off my main domain and the header response is getting to Firefox just fine. All plug-ins, ad-blockers, etc, are disabled and I can see the header in the DOM inspector.
I've tried:
Access-Control-Allow-Origin: *
Access-Control-Allow-Origin: https://services.mywebsite.com
Access-Control-Allow-Origin: http://services.mywebsite.com
Access-Control-Allow-Origin: null
Access-Control-Allow-Origin: "null"
I've verified the SSL Certificate is working just fine (it's a wildcard cert for *.mywebsite.com from Sectigo and I've verified that the entire certification path is working properly)
There are no other response headers except for: X-Frame-Options: SAMEORIGIN ,however, I removed it with the same result.
The site predates CORS by years (ASP.NET Webforms) and there are no other settings I can find that would prevent Firefox from acknowledging this response header.
I've read dozens of posts here (usually someone had a self-signed cert or forgot something) but am at a loss on what is wrong?
Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource at https://services.mywebsite.com/api/geodata/. (Reason: CORS header ‘Access-Control-Allow-Origin’ missing).
It's absolutely NOT MISSING! WTF Firefox?
Pulling hair out here. Anyone?
HTTP/1.1 200 OK
Cache-Control: no-cache
Pragma: no-cache
Transfer-Encoding: chunked
Content-Type: application/json; charset=UTF-8
Expires: -1
Server: Microsoft-IIS/8.5
X-Content-Type-Options: nosniff
Access-Control-Allow-Origin: https://services.mywebsite.com
X-Frame-Options: SAMEORIGIN
Date: Wed, 27 May 2020 08:28:05 GMT
Someone else suggested adding a CORS module to IIS. I did, then added to my web.config file the following (in system.webserver section):
<cors enabled="true">
<add origin="*" allowed="true" >
<allowHeaders allowAllRequestedHeaders="true" />
</add>
</cors>
No Joy! Same message from Firefox (and Chrome) - both browsers completely ignore this directive. Could this be a bug in Mozilla?
-------------------- more info ---------------------------------
I think the problem is with the following jquery script with my CHAT (which is doing the calling to the api). It's worked for 12 years (and still works on old versions), so I'm looking to see what's been deprecated. I suspect that SignalR may be the issue and confusing the browser(s) - since SignalR is making the request (not sure, though -just guessing now). Sorry for not mentioning this sooner.
$.connection.hub.start()
.done(function () {
var existingChatId = getExistingChatId(chatKey);
$.get("https://services.mywebsite.com/api/geodata/", function (response) {
myHub.server.logVisit(document.location.href, document.referrer, response.city_name, response.region_name, response.country_name, existingChatId);
}, "json");
})
.fail(function () { chatRefreshState(false); });
------------------- after using wildcards for CORS headers --------------
HTTP/1.1 200 OK
Cache-Control: no-cache
Pragma: no-cache
Transfer-Encoding: chunked
Content-Type: application/json; charset=UTF-8
Expires: -1
Server: Microsoft-IIS/10.0
X-Content-Type-Options: nosniff
X-SourceFiles: =?UTF-8?B?RDpcU2l0ZXNcaWNhcnBldGlsZXMyXFdlYlxzaWduYWxyXHN0YXJ0?=
X-Powered-By: ASP.NET
X-Frame-Options: SAMEORIGIN
Access-Control-Allow-Origin: *
Access-Control-Allow-Methods: *
Access-Control-Allow-Headers: *
Date: Sun, 07 Jun 2020 10:31:35 GMT
Still no joy - Headers are there. Must be a bug in ASP.NET webforms, IIS, SignalR (please note this is NOT MVC). Time to upgrade this site for this client. No one supports webforms anymore, anyway - it's dead.
It's not possible to do cross domain requests with SignalR and be CORS compatible. There is no way around this problem.
Just move your service to your www.yourwebsite.com and save your hair!
You can install cors dependency:
"Microsoft.AspNet.Cors": "6.0.0-rc1-final"
Add the CORS services in Startup.cs:
public void ConfigureServices(IServiceCollection services)
{
services.AddCors();
}
And enable it for specific domain:
public void Configure(IApplicationBuilder app)
{
app.UseCors(builder =>
builder.WithOrigins("http://example.com"));
}
Another option is enable cors for a specific method:
public class HomeController : Controller
{
[EnableCors("AllowSpecificOrigin")]
public IActionResult Index()
{
return View();
}
}
Or enable it for an specific controller:
[EnableCors("AllowSpecificOrigin")]
public class HomeController : Controller
{
}
If you're using MVC 3, and you have the file Global.asax you can use the method:
protected void Application_BeginRequest(object sender, EventArgs e)
{
HttpContext.Current.Response.AddHeader("Access-Control-Allow-Origin", allowedOrigin);
HttpContext.Current.Response.AddHeader("Access-Control-Allow-Methods", "GET,POST");
}
If you're using WebApi, you might use:
Install-Package Microsoft.AspNet.WebApi.Cors
And register the cors using:
public static void Register(HttpConfiguration config)
{
// New code
config.EnableCors();
}
And:
[EnableCors(origins: "http://example.com", headers: "*", methods: "*")]
public class TestController : ApiController
{
// My methods...
}
Or enable it for whole the project:
public static void Register(HttpConfiguration config)
{
var corsAttr = new EnableCorsAttribute("http://example.com", "*", "*");
config.EnableCors(corsAttr);
}
ASP.Net web forms
Response.AppendHeader("Access-Control-Allow-Origin", "*");
Also try:
Response.AppendHeader("Access-Control-Allow-Methods","*");
Try adding directly in web config:
<system.webServer>
<httpProtocol>
<customHeaders>
<add name="Access-Control-Allow-Methods" value="*" />
<add name="Access-Control-Allow-Headers" value="Content-Type" />
</customHeaders>
</httpProtocol>
</system.webServer>
Do you have app.UseCors() in your middleware pipeline, before app.MapSignalR()?
You can start with app.UseCors(CorsOptions.AllowAll) to check if it'll work and then add your own domain.
app.UseCors(CorsOptions.AllowAll);
app.MapSignalR();

Angular Universal - Pre-render only for web crawlers?

I am intending to user Angular Universal for server side rendering (SSR) but this should only be done for crawlers and bots from selected search engines.
What I want is the following schema:
source: https://dingyuliang.me/use-prerender-improve-angularjs-seo/
After following the official instructions to set up SSR I can now validate that Googlebot (finally) "sees" my website and should be able to index it.
However, at the moment all requests are rendered on the server. Is there a way to determine whether incoming requests are coming from search engines and pre-render the site only for them?
You can achieve that with Nginx.
In Nginx you can forward the request to the universal served angular application via..
if ($http_user_agent ~* "googlebot|yahoo|bingbot") {
proxy_pass 127.0.0.1:5000;
break;
}
root /var/www/html;
..assuming that you are serving angular universal via 127.0.0.1:5000.
In case a browser user agent comes along, we serve the page via root /var/www/html
So the complete config would be something like..
server {
listen 80 default;
server_name angular.local;
location / {
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $remote_addr;
proxy_set_header Host $http_host;
if ($http_user_agent ~* "googlebot|yahoo|bingbot") {
proxy_pass 127.0.0.1:5000;
break;
}
root /var/www/html;
}
}
This is what I came up with IIS:
Add the Angular Universal to your project according to the official guide
In order to get rid of complex folder structures, change the following line in server.ts
const distFolder = join(process.cwd(), 'dist/<Your Project>/browser');
to this:
const distFolder = process.cwd();
Run the npm run build:ssr command. You will end up with the browser and server folders inside the dist folder.
Create a folder for hosting in IIS and copy the files that are in the browser and server folders in to the created folder.
iis\
-assets\
-favicon.ico
-index.html
-main.js => this is the server file
-main-es2015.[...].js
-polyfills-es2015.[...].js
-runtime-es2015.[...].js
-scripts.[...].js
-...
Add a new file to this folder named web.config with this content:
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<system.webServer>
<rewrite>
<rules>
<rule name="Angular Routes" stopProcessing="true">
<match url=".*" />
<conditions logicalGrouping="MatchAll">
<add input="{REQUEST_FILENAME}" matchType="IsFile" negate="true" />
<add input="{REQUEST_FILENAME}" matchType="IsDirectory" negate="true" />
<add input="{HTTP_USER_AGENT}" pattern="(.*[Gg]ooglebot.*)|(.*[Bb]ingbot.*)" negate="true" />
</conditions>
<action type="Rewrite" url="/index.html" />
</rule>
<rule name="ReverseProxyInboundRule1" stopProcessing="true">
<match url=".*" />
<conditions>
<add input="{HTTP_USER_AGENT}" pattern="(.*[Gg]ooglebot.*)|(.*[Bb]ingbot.*)" />
</conditions>
<action type="Rewrite" url="http://localhost:4000/{R:0}" />
</rule>
</rules>
</rewrite>
<directoryBrowse enabled="false" />
</system.webServer>
</configuration>
Inside this folder open a Command Prompt or PowerShell and run the following:
> node main.js
Now you should be able to view your Server-Side Rendered website with localhost:4000 (if you haven't changed the port)
Install the IIS Rewrite Module
Add the folder to your IIS for hosting
IIS will redirect requests that have googlebot or bingbot in them to localhost:4000 which is handled by Express and will return server-side rendered content.
You can test this with Google Chrome, open Developer Console, from the menu select "More tools>Network conditions". Then from the User Agent section disable "Select automatically" and choose Googlebot.
Just managed what you wanted but did not find any anwser providing a detailed step by step with Angular Universal and Express server.
So I post here my solution, any idea of improvement welcomed !
First add this function to the server.ts
function isBot(req: any): boolean {
let botDetected = false;
const userAgent = req.headers['user-agent'];
if (userAgent) {
if (userAgent.includes("Googlebot") ||
userAgent.includes("Bingbot") ||
userAgent.includes("WhatsApp") ||
userAgent.includes("facebook") ||
userAgent.includes("Twitterbot")
) {
console.log('bot detected with includes ' + userAgent);
return true;
}
const crawlers = require('crawler-user-agents');
crawlers.every(entry => {
if (RegExp(entry.pattern).test(userAgent)) {
console.log('bot detected with crawler-user-agents ' + userAgent);
botDetected = true;
return false;
}
return true;
})
if (!botDetected) console.log('bot NOT detected ' + userAgent);
return botDetected;
} else {
console.log('No user agent in request');
return true;
}
}
this function uses 2 modes to detect crawlers (and asumes that the absence of user-agent means that the request is from a bot), the first is a 'simple' manual detection of a string within the header's user-agent and secondly a more advanced detection based on the package 'crawler-user-agents' that you can install to your Angular project like this :
npm install --save crawler-user-agents
Second, once this function added to your server.ts, just use it in each
server.get(`/whatever`, (req: express.Request, res: express.Response) => {
}
of your Express server export function, for which the 'whatever' route should have a different behaviour based on Bot detection.
Your 'server.get()' functions become :
server.get(`/whatever`, (req: express.Request, res: express.Response) => {
if (!isBot(req)) {
// here if bot is not detected we just return the index.hmtl for CSR
res.sendFile(join(distFolder + '/index.html'));
return;
}
// otherwise we prerend
res.render(indexHtml, {
req, providers: [
{ provide: REQUEST, useValue: req }
]
});
});
To further improve the server load for SEO when a bot is requesting a page I also implemented 'node-cache' because in my case SEO bots do not need the very lastest version of each page, for this I found a good answer here :
#61939272

Cross-Origin Request Blocked Issue Between localhost Projects

I wrote a method for uploading files at client side of a webform asp.net, that uses resumablejs plugin.
The other side I wrote a method on controller of a mvc project, and I actived the cors origin in webconfig of this project like as:
<httpProtocol>
<customHeaders>
<add name="Access-Control-Allow-Origin" value="*"/>
<add name="Access-Control-Allow-Methods" value="GET, POST, PUT, DELETE, OPTIONS"/>
<add name="Access-Control-Allow-Headers" value="*"/>
</customHeaders>
</httpProtocol>
I used likem this too:
[EnableCors(origins: "http://localhost:10811", headers: "*", methods: "*")]
public class UploadController : ApiController
{}
but when I call upload method in firefox I have this error in console:
Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource at http://localhost:5023/Home/UploadFiles. (Reason: CORS request did not succeed)
and this error on chrome:
Response for preflight does not have HTTP ok status.
There are a matter : I tested the client side method with a client of mvc project an it's worked.
What is the problem,Can anyone help me?
You will need to an action filter attribute like this:
public class AllowCORSAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
filterContext.RequestContext.HttpContext.Response.AddHeader("Access-Control-Allow-Origin", "*");
base.OnActionExecuting(filterContext);
}
}
Next apply this attribute on your action method or controller class depending upon your requirement. I suggest apply it at action level as it will not make all the action methods cross-orgin accessible:
[AllowCORS]
public ActionResult UploadFile()
Follow these instructions:
Add this section to the web.config of your ASP.NET MVC project inside the <system.webServer> section:
<httpProtocol>
<customHeaders>
<clear/>
<add name="Access-Control-Allow-Origin" value="*"/>
</customHeaders>
</httpProtocol>
In global.asax on the Application_BeginRequest method add some code for preflight response to CORS:
if(HttpContext.Current.Request.HttpMethod == "OPTIONS")
{
//These headers are handling the "pre-flight" OPTIONS call sent by the browser
HttpContext.Current.Response.AddHeader("Access-Control-Allow-Origin",
"*");
HttpContext.Current.Response.AddHeader("Access-Control-Allow-Methods",
"OPTIONS, GET, HEAD, POST, PUT, DELETE");
HttpContext.Current.Response.AddHeader("Access-Control-Allow-Headers",
"Accepts, Content-Type, Origin, Authorization, Api-Version, X-API-KEY, USERGUID");
HttpContext.Current.Response.AddHeader("Access-Control-Max-Age",
"1200");
HttpContext.Current.Response.End();
}
HttpContext.Current.Response.AddHeader("Access-Control-Allow-Methods",
"OPTIONS, GET, HEAD, POST, PUT, DELETE");
HttpContext.Current.Response.AddHeader("Access-Control-Allow-Headers",
"Accepts, Content-Type, Origin, Authorization, Api-Version, X-API-KEY, USERGUID");
HttpContext.Current.Response.AddHeader("Access-Control-Expose-Headers",
"Authorization, Api-Version, USERGUID, WWW-Authenticate");

How to support HTTP OPTIONS verb in ASP.NET MVC/WebAPI application

I've set up an ASP.NET web application starting with an MVC 4/Web API template. It seems as though things are working really well - no problems that I'm aware of. I've used Chrome and Firefox to go through the site. I've tested using Fiddler and all of the responses seem to be on the money.
So now I proceed to write a simple Test.aspx to consume this new Web API. The relevant parts of the script:
<script type="text/javascript">
$(function () {
$.ajax({
url: "http://mywebapidomain.com/api/user",
type: "GET",
contentType: "json",
success: function (data) {
$.each(data, function (index, item) {
....
});
}
);
},
failure: function (result) {
alert(result.d);
},
error: function (XMLHttpRequest, textStatus, errorThrown) {
alert("An error occurred, please try again. " + textStatus);
}
});
});
</script>
This generates a REQUEST header:
OPTIONS http://host.mywebapidomain.com/api/user HTTP/1.1
Host: host.mywebapidomain.com
User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64; rv:24.0) Gecko/20100101 Firefox/24.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Origin: http://mywebapidomain.com
Access-Control-Request-Method: GET
Access-Control-Request-Headers: content-type
Connection: keep-alive
As is, Web API returns a 405 Method Not Allowed.
HTTP/1.1 405 Method Not Allowed
Cache-Control: no-cache
Pragma: no-cache
Content-Type: application/xml; charset=utf-8
Expires: -1
Server: Microsoft-IIS/8.0
X-AspNet-Version: 4.0.30319
X-Powered-By: ASP.NET
Date: Mon, 30 Sep 2013 13:28:12 GMT
Content-Length: 96
<Error><Message>The requested resource does not support http method 'OPTIONS'.</Message></Error>
I understand that the OPTIONS verb is not wired up in Web API controllers by default... So, I placed the following code in my UserController.cs:
// OPTIONS HTTP-verb handler
public HttpResponseMessage OptionsUser()
{
var response = new HttpResponseMessage();
response.StatusCode = HttpStatusCode.OK;
return response;
}
...and this eliminated the 405 Method Not Allowed error, but the response is completely empty - no data is returned:
HTTP/1.1 200 OK
Cache-Control: no-cache
Pragma: no-cache
Expires: -1
Server: Microsoft-IIS/8.0
X-AspNet-Version: 4.0.30319
X-Powered-By: ASP.NET
Date: Mon, 30 Sep 2013 12:56:21 GMT
Content-Length: 0
There must be additional logic... I don't know how to properly code the Options method or if the controller is even the proper place to put the code. Weird (to me) that the Web API site responds properly when viewed from Firefox or Chrome, yet the .ajax call above errors out. How do I handle the "preflight" check in the .ajax code? Maybe I should be addressing this issue on the client side's .ajax logic? Or, if this is an issue on the server side due to not handling the OPTIONS verb.
Can anyone help? This must be a very common issue and I apologize if it's been answered here. I searched but didn't find any answers that helped.
UPDATE
IMHO, this is a client-side issue and has to do with the Ajax JQuery code above. I say this because Fiddler doesn't show any 405 error headers when I access mywebapidomain/api/user from a web browser. The only place I can duplicate this problem is from the JQuery .ajax() call. Also, the identical Ajax call above works fine when run on the server (same domain).
I found another post: Prototype AJAX request being sent as OPTIONS rather than GET; results in 501 error that seems to be related, but I've tinkered with their suggestions with no success. Apparently, JQuery is coded so that if an Ajax request is cross-domain (which mine is) it adds a couple of headers that trigger the OPTIONS header somehow.
'X-Requested-With': 'XMLHttpRequest',
'X-Prototype-Version': Prototype.Version,
It just seems that there should be a better solution available than modifying core code in JQuery...
The answer provided below assumes this is a server-side issue. Maybe, I guess, but I lean toward clients, and calling a hosting provider isn't going to help.
Mike Goodwin answer is great but it seemed, when I tried it, that it was aimed at MVC5/WebApi 2.1.
The dependencies for Microsoft.AspNet.WebApi.Cors didn't play nicely with my MVC4 project.
The simplest way to enable CORS on WebApi with MVC4 was the following.
Note that I have allowed all, I suggest you limit the Origin's to just the clients you want your API to serve. Allowing everything is a security risk.
Web.config:
<system.webServer>
<httpProtocol>
<customHeaders>
<add name="Access-Control-Allow-Origin" value="*" />
<add name="Access-Control-Allow-Methods" value="GET, PUT, POST, DELETE, HEAD" />
<add name="Access-Control-Allow-Headers" value="Origin, X-Requested-With, Content-Type, Accept" />
</customHeaders>
</httpProtocol>
</system.webServer>
BaseApiController.cs:
We do this to allow the OPTIONS http verb
public class BaseApiController : ApiController
{
public HttpResponseMessage Options()
{
return new HttpResponseMessage { StatusCode = HttpStatusCode.OK };
}
}
As Daniel A. White said in his comment, the OPTIONS request is most likely created by the client as part of a cross domain JavaScript request. This is done automatically by Cross Origin Resource Sharing (CORS) compliant browsers. The request is a preliminary or pre-flight request, made before the actual AJAX request to determine which request verbs and headers are supported for CORS. The server can elect to support it for none, all or some of the HTTP verbs.
To complete the picture, the AJAX request has an additional "Origin" header, which identified where the original page which is hosting the JavaScript was served from. The server can elect to support request from any origin, or just for a set of known, trusted origins. Allowing any origin is a security risk since is can increase the risk of Cross site Request Forgery (CSRF).
So, you need to enable CORS.
Here is a link that explains how to do this in ASP.Net Web API
http://www.asp.net/web-api/overview/security/enabling-cross-origin-requests-in-web-api#enable-cors
The implementation described there allows you to specify, amongst other things
CORS support on a per-action, per-controller or global basis
The supported origins
When enabling CORS a a controller or global level, the supported HTTP verbs
Whether the server supports sending credentials with cross-origin requests
In general, this works fine, but you need to make sure you are aware of the security risks, especially if you allow cross origin requests from any domain. Think very carefully before you allow this.
In terms of which browsers support CORS, Wikipedia says the following engines support it:
Gecko 1.9.1 (FireFox 3.5)
WebKit (Safari 4, Chrome 3)
MSHTML/Trident 6 (IE10) with partial support in IE8 and 9
Presto (Opera 12)
http://en.wikipedia.org/wiki/Cross-origin_resource_sharing#Browser_support
Just add this to your Application_OnBeginRequest method (this will enable CORS support globally for your application) and "handle" preflight requests :
var res = HttpContext.Current.Response;
var req = HttpContext.Current.Request;
res.AppendHeader("Access-Control-Allow-Origin", req.Headers["Origin"]);
res.AppendHeader("Access-Control-Allow-Credentials", "true");
res.AppendHeader("Access-Control-Allow-Headers", "Content-Type, X-CSRF-Token, X-Requested-With, Accept, Accept-Version, Content-Length, Content-MD5, Date, X-Api-Version, X-File-Name");
res.AppendHeader("Access-Control-Allow-Methods", "POST,GET,PUT,PATCH,DELETE,OPTIONS");
// ==== Respond to the OPTIONS verb =====
if (req.HttpMethod == "OPTIONS")
{
res.StatusCode = 200;
res.End();
}
* security: be aware that this will enable ajax requests from anywhere to your server (you can instead only allow a comma separated list of Origins/urls if you prefer).
I used current client origin instead of * because this will allow credentials => setting Access-Control-Allow-Credentials to true will enable cross browser session managment
also you need to enable delete and put, patch and options verbs in your webconfig section system.webServer, otherwise IIS will block them :
<handlers>
<remove name="ExtensionlessUrlHandler-ISAPI-4.0_32bit" />
<remove name="ExtensionlessUrlHandler-ISAPI-4.0_64bit" />
<remove name="ExtensionlessUrlHandler-Integrated-4.0" />
<add name="ExtensionlessUrlHandler-ISAPI-4.0_32bit" path="*." verb="GET,HEAD,POST,DEBUG,PUT,DELETE,PATCH,OPTIONS" modules="IsapiModule" scriptProcessor="%windir%\Microsoft.NET\Framework\v4.0.30319\aspnet_isapi.dll" preCondition="classicMode,runtimeVersionv4.0,bitness32" responseBufferLimit="0" />
<add name="ExtensionlessUrlHandler-ISAPI-4.0_64bit" path="*." verb="GET,HEAD,POST,DEBUG,PUT,DELETE,PATCH,OPTIONS" modules="IsapiModule" scriptProcessor="%windir%\Microsoft.NET\Framework64\v4.0.30319\aspnet_isapi.dll" preCondition="classicMode,runtimeVersionv4.0,bitness64" responseBufferLimit="0" />
<add name="ExtensionlessUrlHandler-Integrated-4.0" path="*." verb="GET,HEAD,POST,DEBUG,PUT,DELETE,PATCH,OPTIONS" type="System.Web.Handlers.TransferRequestHandler" preCondition="integratedMode,runtimeVersionv4.0" />
</handlers>
hope this helps
After encountering the same issue in a Web API 2 project (and being unable to use the standard CORS packages for reasons not worth going into here), I was able to resolve this by implementing a custom DelagatingHandler:
public class AllowOptionsHandler : DelegatingHandler
{
protected override async Task<HttpResponseMessage> SendAsync(
HttpRequestMessage request, CancellationToken cancellationToken)
{
var response = await base.SendAsync(request, cancellationToken);
if (request.Method == HttpMethod.Options &&
response.StatusCode == HttpStatusCode.MethodNotAllowed)
{
response = new HttpResponseMessage(HttpStatusCode.OK);
}
return response;
}
}
For the Web API configuration:
config.MessageHandlers.Add(new AllowOptionsHandler());
Note that I also have the CORS headers enabled in Web.config, similar to some of the other answers posted here:
<system.webServer>
<modules runAllManagedModulesForAllRequests="true">
<remove name="WebDAVModule" />
</modules>
<httpProtocol>
<customHeaders>
<add name="Access-Control-Allow-Origin" value="*" />
<add name="Access-Control-Allow-Headers" value="accept, cache-control, content-type, authorization" />
<add name="Access-Control-Allow-Methods" value="GET, POST, PUT, DELETE, OPTIONS" />
</customHeaders>
</httpProtocol>
<handlers>
<remove name="ExtensionlessUrlHandler-Integrated-4.0" />
<remove name="TRACEVerbHandler" />
<add name="ExtensionlessUrlHandler-Integrated-4.0" path="*." verb="*" type="System.Web.Handlers.TransferRequestHandler" preCondition="integratedMode,runtimeVersionv4.0" />
</handlers>
</system.webServer>
Note that my project does not include MVC, only Web API 2.
I have managed to overcome 405 and 404 errors thrown on pre-flight ajax options requests only by custom code in global.asax
protected void Application_BeginRequest()
{
HttpContext.Current.Response.AddHeader("Access-Control-Allow-Origin", "*");
if (HttpContext.Current.Request.HttpMethod == "OPTIONS")
{
//These headers are handling the "pre-flight" OPTIONS call sent by the browser
HttpContext.Current.Response.AddHeader("Access-Control-Allow-Methods", "GET, OPTIONS");
HttpContext.Current.Response.AddHeader("Access-Control-Allow-Headers", "Content-Type, Accept");
HttpContext.Current.Response.AddHeader("Access-Control-Max-Age", "1728000");
HttpContext.Current.Response.End();
}
}
PS: Consider security issues when allowing everything *.
I had to disable CORS since it was returning 'Access-Control-Allow-Origin' header contains multiple values.
Also needed this in web.config:
<handlers>
<remove name="ExtensionlessUrlHandler-Integrated-4.0"/>
<remove name="OPTIONSVerbHandler"/>
<remove name="TRACEVerbHandler"/>
<add name="ExtensionlessUrlHandler-Integrated-4.0" path="*." verb="*" type="System.Web.Handlers.TransferRequestHandler" preCondition="integratedMode,runtimeVersionv4.0"/>
</handlers>
And app.pool needs to be set to Integrated mode.
I had this same problem. For me the fix was to remove the custom content type from the jQuery AJAX call. Custom content types trigger the pre-flight request. I found this:
The browser can skip the preflight request if the following conditions are true:
The request method is GET, HEAD, or POST, and
The application does not set any request headers other than Accept, Accept-Language, Content-Language, Content-Type, or Last-Event-ID, and
The Content-Type header (if set) is one of the following:
application/x-www-form-urlencoded
multipart/form-data
text/plain
From this page: http://www.asp.net/web-api/overview/security/enabling-cross-origin-requests-in-web-api (under "Preflight Requests")
In ASP.NET web api 2 , CORS support has been added . Please check the link [ http://www.asp.net/web-api/overview/security/enabling-cross-origin-requests-in-web-api ]
protected void Application_EndRequest()
{
if (Context.Response.StatusCode == 405 && Context.Request.HttpMethod == "OPTIONS" )
{
Response.Clear();
Response.StatusCode = 200;
Response.End();
}
}
I too faced the same issue.
Follow the below step to solve the issue on (CORS) compliance in browsers.
Include REDRock in your solution with the Cors reference.
Include WebActivatorEx reference to Web API solution.
Then Add the file CorsConfig in the Web API App_Start Folder.
[assembly: PreApplicationStartMethod(typeof(WebApiNamespace.CorsConfig), "PreStart")]
namespace WebApiNamespace
{
public static class CorsConfig
{
public static void PreStart()
{
GlobalConfiguration.Configuration.MessageHandlers.Add(new RedRocket.WebApi.Cors.CorsHandler());
}
}
}
With these changes done i was able to access the webapi in all browsers.
I've had same problem, and this is how I fixed it:
Just throw this in your web.config:
<system.webServer>
<modules>
<remove name="WebDAVModule" />
</modules>
<httpProtocol>
<customHeaders>
<add name="Access-Control-Expose-Headers " value="WWW-Authenticate"/>
<add name="Access-Control-Allow-Origin" value="*" />
<add name="Access-Control-Allow-Methods" value="GET, POST, OPTIONS, PUT, PATCH, DELETE" />
<add name="Access-Control-Allow-Headers" value="accept, authorization, Content-Type" />
<remove name="X-Powered-By" />
</customHeaders>
</httpProtocol>
<handlers>
<remove name="WebDAV" />
<remove name="ExtensionlessUrlHandler-Integrated-4.0" />
<remove name="TRACEVerbHandler" />
<add name="ExtensionlessUrlHandler-Integrated-4.0" path="*." verb="*" type="System.Web.Handlers.TransferRequestHandler" preCondition="integratedMode,runtimeVersionv4.0" />
</handlers>
</system.webServer>
//In the Application_OnBeginRequest method in GLOBAL.ASX add the following:-
var res = HttpContext.Current.Response;
var req = HttpContext.Current.Request;
res.AppendHeader("Access-Control-Allow-Origin", "*");
res.AppendHeader("Access-Control-Allow-Credentials", "true");
res.AppendHeader("Access-Control-Allow-Headers", "Authorization");
res.AppendHeader("Access-Control-Allow-Methods", "POST,GET,PUT,PATCH,DELETE,OPTIONS");
// ==== Respond to the OPTIONS verb =====
if (req.HttpMethod == "OPTIONS")
{
res.StatusCode = 200;
res.End();
}
//Remove any entries in the custom headers as this will throw an error that there's to
//many values in the header.
<httpProtocol>
<customHeaders>
</customHeaders>
</httpProtocol>

Resources