I have a SignalR server hosted and running in a Owin Web API application running in Azure (Company Service App). From a separate web application, let's say application A (non Azure) I'm connecting to SignalR using the javascript generated proxies:
Where CompanyServiceBaseUrl is set to my site url: (e.g. "http:// mysite/companyservice")
<script src="#Url.Content(CompanyServiceBaseUrl + "/Scripts/jquery.signalR-2.2.0.js")"></script>
<script src="#Url.Content(CompanyServiceBaseUrl + "/signalr/hubs")" type="text/javascript"></script>
<script type="text/javascript">
var hub = $.connection.perform;
hub.client.onToast = function () {
$("#copyOrganizationSuccessAlert").show();
};
$(document).ready(function() {
var event = document.createEvent('Event');
event.initEvent('onOrganizationLock', true, true);
onOrganizationLockEvent = function() {
document.dispatchEvent(event);
};
hub.client.onOrganizationLock = onOrganizationLockEvent;
});
$.connection.hub.start().done(function () {
hub.server.joinGroup(perform.company.application.clientId);
});
</script>
Here is my generated folder structure in my client:
When I test locally the SignalR connectivity works fine, but when I test in my environment hosted in Azure connectivity fails.
SignalR negotiate request (between App A and CompanyService):
http://<mysiteurl>/signalr/negotiate (notice that companyservice is missing from URL:
it should be:
http://<mysiteurl>/companyservice/signalr/negotiate
Here the Reverse proxy rule:
<rule name="companyservice" stopProcessing="true">
<match url="^companyservice/(.*)" />
<conditions>
<add input="{CACHE_URL}" pattern="^(https?)://" />
</conditions>
<action type="Rewrite" url="https://myazuresite.azurewebsites.net/{R:1}" />
<serverVariables>
<set name="HTTP_ACCEPT_ENCODING" value="" />
</serverVariables>
</rule>
</rules>
Question:
Why is my client connection.Url coming as "/signalr" instead of "/companyservice/signalr" when testing with my Azure site using a reverse proxy?
Finally, looking at this reported SignalR issue, looks like he is experience a very similar thing:
SignalR doesn't work behind a Reverse Proxy
But I'd like to understand what he means by:
"If I use a Reverse-Proxy rule (scan and replace) and set "Url":"/NorthboundWebApiClient/signalr" everything works fine.
I have tried establishing a connection with and without generated proxies specifying my connection url manually var connection = $.hubConnection('/companyservice/signalr', {useDefaultPath: false});, configuring CORS in the client side and no luck.
Here is a related post having problems with cross-domains:
How do I get a SignalR hub connection to work cross-domain?
Four months later a colleague suggested a solution which worked perfectly. His idea was to create a custom custom OwinMiddleware that would add the missing Azure virtual directory to the path in case missing. Something like this:
public class SignalRvDirMiddleware : OwinMiddleware
{
public SignalRvDirMiddleware(OwinMiddleware next)
: base(next)
{ }
public override async Task Invoke(IOwinContext context)
{
var vdir = new PathString("/companyservice");
PathString path = context.Request.PathBase;
if (!path.StartsWithSegments(vdir))
{
context.Request.PathBase = vdir + path;
}
await Next.Invoke(context);
}
}
And then just hook it up in your Owin Configuration method on Startup.cs :
public void Configuration(IAppBuilder appBuilder)
{
// Branch the pipeline here for requests that start with "/signalr"
appBuilder.Map("/signalr", map =>
{
map.Use<SignalRvDirMiddleware>();
map.RunSignalR(hubConfiguration);
});
}
The credit goes to my colleague Jake S.
Related
I'm trying to integrate Signalr into my web form project.
First, added the references using nugget
Microsoft.AspNet.SignalR.Client;
Microsoft.AspNet.SignalR.Core;
Microsoft.AspNet.SignalR.System.Web;
Microsoft.Owin;
Microsoft.Owin.Host.SystemWeb;
Microsoft.Owin.Security;
Owin
My startup class:
[assembly: OwinStartup(typeof(ns.App_Code.Startup))]
namespace ns
{
public class Startup
{
public void Configuration(IAppBuilder app)
{
app.MapSignalR();
}
}
}
My hub:
public class PositionHub : Hub
{
static ConcurrentDictionary<int, List<string>> connectedUsers = new ConcurrentDictionary<int, List<string>>();
public override Task OnConnected()
{
return base.OnConnected();
}
}
Finally, the client-side:
<script type="text/javascript" src="Scripts/jquery-2.2.4.min.js"></script>
<script src="Scripts/jquery.signalR-2.3.0.min.js"></script>
<script src="<%= ResolveUrl("~/signalr/hubs") %>" type="text/javascript"></script>
<script type="text/javascript">
$(function() {
var logger = $.connection.positionHub;
logger.start();
});
</script>
Also, added to web.config the following:
<add key="owin:AutomaticAppStartup" value="true" />
and
<system.webServer>
<validation validateIntegratedModeConfiguration="false" />
<modules runAllManagedModulesForAllRequests="true" />
When I run the app it gives the 404 error:
GET http://localhost:41363/signalr/hubs 404 (Not Found)
This is not my first time using signalr, but it is the first time I have this issue, and been struggling for hours to solve it, with no luck...
I notice you don't have the following NuGet packages installed which are required when integrating SignalR with Javascript.
Microsoft.AspNet.SignalR
This package pulls in the server components and JavaScript client required to use SignalR in an ASP.NET application.
Microsoft.AspNet.SignalR.JS
Script client for ASP.NET SignalR.
(This one is included automatically by Microsoft.AspNet.SignalR above.)
EDIT
Via the comments below we found out that OP's web.configfile contained some url rewrite rules which intercepted the /signalr/hubs url.
Adding a rule to prevent this url from being rewritten solved the problem.
<rule name="signalR" enabled="true" stopProcessing="true">
<match url="^signalr.*" /> <action type="None" />
</rule>
fix startup class namespace
namespace ns
to be
namespace TeltonikaSiteWebApplication.App_Code
The rewrite rule was conflicting it on web config was conflicting with signalr. I needed to add a new one to make it work. . Thanks to #pfx on the guidance.
So I am very new to SignalR, in fact I've only been using it for a couple of days now. Anyway, I am getting the error below when my application first starts up:
The code for the application in question is located in two projects, a Web API and a Single Page Application (SPA). The first one has my backend code (C#) and the second one my client-side code (AngularJS). I think the problem might be due to the fact that the projects in question run on different ports. The Web API, where my SignalR hub lives, is on port 60161 and the SPA is on 60813. My hub is declared like so:
public class ReportHub : Hub
{
public void SendReportProgress(IList<ReportProgress> reportProgress)
{
this.Clients.All.broadcastReportProgress(reportProgress);
}
public override Task OnConnected()
{
this.Clients.All.newConnection();
return base.OnConnected();
}
}
and then in my Startup.cs file for my Web API I initialize SignalR like this:
public void Configuration(IAppBuilder app)
{
HttpConfiguration config = new HttpConfiguration();
config.Services.Replace(typeof(IHttpControllerActivator), new NinjectFactory());
config.MessageHandlers.Add(new MessageHandler());
//set up OAuth and Cors
this.ConfigureOAuth(app);
config.EnableCors();
config.IncludeErrorDetailPolicy = IncludeErrorDetailPolicy.Always;
// Setting up SignalR
app.Map("/signalr", map =>
{
map.UseCors(CorsOptions.AllowAll);
map.RunSignalR(new HubConfiguration { EnableJSONP = true });
});
//set up json formatters
FormatterConfig.RegisterFormatters(config.Formatters);
WebApiConfig.Register(config);
app.UseWebApi(config);
}
For my client-side code I use an Angular SignalR API called angular-signalr-hub (Angular-signalr-hub). The client-side follows:
angular
.module("mainApp")
.factory("reportHubService", ["$rootScope", "Hub", reportHubService]);
/// The factory function
function reportHubService($rootScope, Hub) {
var vm = this;
vm.reportName = "None";
// Setting up the SignalR hub
var hub = new Hub("reportHub", {
listeners: {
'newConnection': function(id) {
vm.reportName = "SignalR connected!";
$rootScope.$apply();
},
'broadcastReportProgress': function (reportProgress) {
vm.reportName = reportProgress.reportName;
$rootScope.$apply();
}
},
errorHandler: function(error) {
},
hubDisconnected: function () {
if (hub.connection.lastError) {
hub.connection.start();
}
},
transport: 'webSockets',
logging: true
//rootPath: 'http://localhost:60161/signalr'
});
I did some googling yesterday and one of the suggestions I came upon was to set the SignalR URL to the one of my Web API, which I did (the commented out line above). When I uncomment the line in question, that does seem to do something because if I now go to http://localhost:60161/signalr/hubs in my browser, it does show me the dynamically generated proxy file:
and when I run my application I no longer get the error above, but now it doesn't seem to connect. It gets to the negotiate line and it stops there:
I think it should look like this (this is from a SignalR tutorial I found):
In addition, none of my listeners (declared in my Angular code above) get called, so something is still now working quite right. There should be more lines in the log to the effect that connection was successfully established, etc. What could be the problem here?
UPDATE: upon further debugging i found out the problem is most likely being caused by the ProtocolVersion property being different between the client and the result here:
Because of that it seems it just exists and fails to establish connection.
I figured out what the problem was. My SignalR dependencies were out of date and because of that my client and server versions differed. All I had to do was update (via NuGet Package Manager) all SignalR dependencies to the latest version and now it works.
As a side note, SignalR was not very good at telling me what was wrong. In fact, no error message was displayed, unless of course there was some additional logging somewhere that had to be found or turned on, in addition to the logging I already had (turned on). Either way, it's either not logging certain errors or it makes it difficult to figure out how to turn on all logging. I had to go and debug the JQuery SignalR api to figure out what the problem was, which was a time consuming endeavour.
I have three applications in my architecture.
They are on the same server but having different port numbers.
A - Token Application (port 4444) - Asp.net WebApi
B - API Application (port 3333) - Asp.net WebApi
C - UI Application (port 2222) - AngularJS App.
The application flow is like below
1- The UI project gets the token from Token Application (It requires Windows Auth.)
Ex : awxrsdsaWeffs12da
2- UI application puts this token to a custom header which is named as "accessToken"
Ex : accessToken : awxrsdsaWeffs12da
3- UI application sends a request to API Application
Ex: http:myaddress:3333/api/TheRestServiceHere
UI application gets 401 Error.
Which sends OPTIONS method. (I guess preflight issue)
In my web api project I enabled Cors like this below.
public static void Register(HttpConfiguration config)
{
....
//CORS
var cors = new EnableCorsAttribute("*", "*", "*");
config.EnableCors(cors);
....
}
Config
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
//CORS
var cors = new EnableCorsAttribute("*", "*", "*");
config.EnableCors();
// Web API routes
config.MapHttpAttributeRoutes();
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
var json = config.Formatters.JsonFormatter;
json.SerializerSettings.PreserveReferencesHandling = PreserveReferencesHandling.None;
json.SerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore;
json.SerializerSettings.Formatting = Formatting.None;
json.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver();
config.Formatters.Remove(config.Formatters.XmlFormatter);
}
}
So I am looking for a solution to call API application (B) controllers
and get 200 :)
Regards
I fixed this in an application I am working on by creating a module that responds to requests that are using the OPTIONS verb. You should probably modify it a bit to include the verbs and content type that the application is requesting. In my case, I decided to post everything as JSON (which requires the pre-flight check). The module is as follows:
public class OptionsModule : IHttpModule
{
public void Init(HttpApplication context)
{
context.BeginRequest += (sender, args) =>
{
var app = (HttpApplication) sender;
if (app.Request.HttpMethod == "OPTIONS")
{
app.Response.StatusCode = 200;
app.Response.AddHeader("Access-Control-Allow-Headers", "content-type");
app.Response.AddHeader("Access-Control-Allow-Origin", APISettings.ApplicationOrigin);
app.Response.AddHeader("Access-Control-Allow-Credentials", "true");
app.Response.AddHeader("Access-Control-Allow-Methods", "POST,GET,OPTIONS");
app.Response.AddHeader("Content-Type", "application/json");
app.Response.End();
}
};
}
public void Dispose()
{
}
}
Then you need to register it in your web.config:
<system.webServer>
<modules>
<add name="HandleOptions" type="namespace.OptionsModule" />
</modules>
</system.webServer>
Another thing you may want to do is specify the allowed origin explicitly. Chrome doesn't like having a wildcard there.
One of my friend solved the issue by using OPTIONSVerbHandler.
When UI application wants to use GET method, browser sends
OPTION method first to the server (Preflight). Then if Preflight request is OK it sends GET request.
For CORS test purpose we used the following code to send GET method.
<html>
<head>
<script src="https://code.jquery.com/jquery-1.9.1.min.js"></script>
<script>
$( document ).ready(function() {
var adress = "http://10.10.27.36:3434/backend/api/role";
$.ajaxSetup({
headers: {
'Content-Type': 'application/json',
'accessToken': 'some value',
'Origin' : ''
}
});
$.ajax({
type: "GET",
url: adress,
dataType: "json"
});
});
</script></head><body></body></html>
To handle OPTION method which sends by browser before GET you should have the following settings.
1- Webconfig
<system.webServer>
<handlers>
<add name="OPTIONSVerbHandler" path="*" verb="OPTIONS" modules="ProtocolSupportModule" resourceType="Unspecified" requireAccess="None" />
</handlers>
</system.webServer>
2- Adding OPTIONSVerbHandler with following settings
Click on request restrictions
3- Our Header Settings we have accessToken which is custom as you can see
This problem happened for Cordova v11, platform for Android. I used the solution provided by Jereme (the top rated answer) with one exception: In OptionsModule, I had to omit the statement
app.Response.AddHeader("Access-Control-Allow-Origin", APISettings.ApplicationOrigin);
Instead in the web.config file I added in the <system.webServer> section the following:
<httpProtocol>
<customHeaders>
<add name="Access-Control-Allow-Origin" value="*" />
<!-- Note: "localhost" for Cordova is not valid, only * worked. -->
</customHeaders>
</httpProtocol>
A word about Cordova in case one is unfamiliar. Cordova packages a native app for the Android platform with a “javascript page” included in the app, using a web view in the app to display the page. Using the Chrome debugger to view the javascript page, the page origin appears as localhost. However, localhost is not an acceptable value for the Access-Control-Allow-Origin header; therefore I had to use “*” in the Web.config file. When I had the response header in the OptionsModule, the preflight response was ok (status 200), but not the response for the XMLHttpRequest (the api) that initiated the preflight request. Putting the custom header only in the Web.config file allowed both responses to be ok (status 200).
For Azure Environment
You need allow origins from the portal.
I've re-asked this question with examples provided on github and a drop box download link for anyone that want to run the code themselves : Swagger not working on a self hosted ServiceStack Service
I had my servicestack JSON service running inside my website solution, under the '/api/ path, but now I'd like to split out that service stack portion and have it running as a self hosted windows service. My problem is, myself and the other developers find the Swagger plugin very useful for testing purposes, but now that it's self hosted it appears the HTTPHandler is setup only for only handling the service routes, and plain HTML does not work.
How do I fix this?
URL : http://localhost:8081/Swagger-UI/Index.html
Response :
Handler for Request not found:
Request.HttpMethod: GET
Request.HttpMethod: GET
Request.PathInfo: /Swagger-UI/Index.html
Request.QueryString:
Request.RawUrl: /Swagger-UI/Index.html
Nuget packages installed :
ServiceStack
ServiceStack.Razor
Update for #marfarma
My app.config file has nothing ServiceStack related inside it...
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5" />
</startup>
<system.serviceModel>
<bindings>
<basicHttpBinding>
<binding name="BasicHttpBinding_service1_V2_SSL" />
<binding name="BasicHttpBinding_service2_V1_SSL" />
</basicHttpBinding>
</bindings>
<client>
<!-- bespoke services I'm connecting too -->
</client>
</system.serviceModel>
<appSettings>
<!-- bespoke app settings -->
</appSettings>
</configuration>
Program.cs :
AppHostHttpListenerBase appHost = new my.HttpApi.myApiServiceHostBase("My Service", "my.ApiService");
string listeningURL = "http://localhost:8081/";
var appSettings = new ServiceStack.Configuration.AppSettings();
my.HttpApi.FakeProvider.ProductProvider.Init(appSettings);
my.HttpApi.FakeProvider.UserProvider.Init(appSettings);
#if DEBUG
try
{
appHost.Init();
appHost.Start(listeningURL);
Console.WriteLine("Press <CTRL>+C to stop.");
Thread.Sleep(Timeout.Infinite);
}
catch (Exception ex)
{
Console.WriteLine("ERROR: {0}: {1}", ex.GetType().Name, ex.Message);
throw;
}
finally
{
appHost.Stop();
}
Console.WriteLine("WinServiceAppHost has finished");
configure method :
public override void Configure(Funq.Container container)
{
Plugins.RemoveAll(x => x is CsvFormat);
Plugins.RemoveAll(x => x is HtmlFormat);
ServiceStack.Text.JsConfig.EmitCamelCaseNames = true;
//register any dependencies your services use, e.g:
//container.Register<ICacheClient>(new MemoryCacheClient());
var config = new EndpointHostConfig { DefaultContentType = ContentType.Json, ServiceStackHandlerFactoryPath = "api" };
SetConfig(config);
Plugins.Add(new ServiceStack.Api.Swagger.SwaggerFeature());
Dictionary<Type, string[]> serviceRoutes = new Dictionary<Type, string[]>();
serviceRoutes.Add(typeof(AuthService), new[] { "/auth/user" });
AuthFeature authFeature = new AuthFeature(() => new AuthUserSession(), new IAuthProvider[] { new FakeCredentialsAuthProvider() });
authFeature.IncludeAssignRoleServices = false;
authFeature.ServiceRoutes = serviceRoutes; //specify manual auth routes
Plugins.Add(authFeature);
container.Register<ICacheClient>(new MemoryCacheClient());
var userRep = new InMemoryAuthRepository();
container.Register<IUserAuthRepository>(userRep);
Plugins.Add(new CorsFeature(allowedOrigins: "*",
allowedMethods: "GET, POST, OPTIONS",
//allowedHeaders: "Content-Type",
allowedHeaders : "Origin, X-Atmosphere-tracking-id, X-Atmosphere-Framework, X-Cache-Date, Content-Type, X-Atmosphere-Transport, *",
allowCredentials: false));
}
Update 2 :
1.) Removed the plugins sections above that is removing html and csv
2.) Ran the following in the package manager (nuget) :
Install-Package ServiceStack.Api.Swagger
still no handler found.
The advice in this question ( ServiceStack: No /swagger-ui/index.html ) that marfarma pointed me at suggests I may not have the 'swagger-ui' html and javascript present. I do.
It appears the self hosted servicestack only 'handles' the specified routes, and when I ask the self hosted service to deliver the swagger html and javascript, I get the "handler for request not found" error above.
When I visit the /resources address in my self hosted service, it shows the expected page and data (suggesting the swagger plugin is doing its thing properly), but when I load up the swagger html and javascript from the file system (not 'served' by my service), and provide it the resource url that works, I get "Can't read from server. It may not have the appropriate access-control-origin settings.", which appears to be a CORS issue, but I've enabled cors on my service (it appears an issue with the Swagger UI code), the swagger-ui send my self hosted service a request sent as 'OPTIONS' (instead of GET or POST), and the options request fails :(
as answered in Swagger not working on a self hosted ServiceStack Service:
self hosted services are serving files from bin/debug -> copy always or copy if newer is needed.
I am just learning ASP.NET MVC, and I have recently found the [RequireHttps] attribute to automatically redirect a GET request to use SSL, like so...
[RequireHttps] //apply to all actions in controller
public class SomeController
{
[RequireHttps] //apply to this action only
public ActionResult SomeAction()
{
...
}
}
When using IIS Express as the development server, this successfully redirects the request from http://localhost:55945/... to https://localhost/....
However on my development system, my project is using HTTPS on port 44300 (this was configured automatically by Visual Studio 2010), and I have not yet found any way to tell MVC to use that port number in the redirection, so that it goes to the required https://localhost:43300/... instead.
I really expected this to be automatic, given the SSL port number was set automatically by Visual Studio, and as far as I can tell, this must be effecting all developers using the [RequireHttps] attribute in MVC3. In my searching for a solution, I have seen a few patchy "work around" solutions, but nothing that appears to be definitively "the right way" to fix it.
So doing things "the right way", what do I change (either in the source code, or in my project configuration) to tell the [RequireHttps] attribute in MVC3 to use the HTTPS port that my project is configured to be using?
Or alternatively, is there some other completely different and better "right way" to set up SSL support in an MVC3 project, that does not have this problem?
The RequireHttpsAttribute is quite simple and it cannot be parametrized to redirect to a specific port. If you really need this you could create a subclass and override the HandleNonHttpsRequest method to compose the redirect url differently.
protected override void HandleNonHttpsRequest(AuthorizationContext filterContext) {
base.HandleNonHttpsRequest(filterContext);
// redirect to HTTPS version of page
string url = "https://" + filterContext.HttpContext.Request.Url.Host + ":" + MyConfig.SslPort + filterContext.HttpContext.Request.RawUrl;
filterContext.Result = new RedirectResult(url);
}
However, if your entire site runs with HTTPS you could just configure in VS in the web project properties (Web -> Start Action -> Start URL) to open the correct HTTPS url with your port and not use the redirect feature for local testing.
I have this same issue, and I'm solving it by using both the RequireHttpsAttribute and a URL rewrite rule in Web.config. The rewrite rule matches the nonstandard port numbers, and executes ahead of the attribute. You can use a Web.config transform to remove the rewrite rule on deployment, but if you leave it in it shouldn't have any effect. In production you'll use standard port number that the rule won't match. Then the attribute will catch it.
Here's the rule:
<system.webServer>
<rewrite>
<rules>
<!-- Redirect HTTP requests to HTTPS, using the non-standard development ports.
On deployment, this rule is removed, and the RequireHttpAttribute filter globally applied
in SlicerWeb.FilterConfig takes over. This rewrite rule executes before the attribute
would be applied, and so can apply the port numbers -->
<rule name="HTTPS redirect" stopProcessing="true">
<match url="(.*)" />
<conditions>
<add input="{HTTPS}" pattern="off" ignoreCase="true" />
<add input="{SERVER_PORT}" pattern="60470" />
</conditions>
<action type="Redirect" url="https://{SERVER_NAME}:44300/{R:1}" redirectType="Found" />
</rule>
</rules>
</rewrite>
</system.webServer>
The downside to this approach is that you're not relying on the attribute when running locally. But if you've applied it once globally, instead of adding it to every controller, that's OK in my opinion.
A few things that might be helpful.
There is a version of the source code of RequireHttpsAttribute in this thread: Where is the source for RequireHttpsAttribute?
There is also a nearly identical class called RequireSslAttribute on codeplex mentioned on the same thread. http://aspnet.codeplex.com/SourceControl/changeset/view/63930#391756
Here is a sample of an attribute that can be used to toggle either from http to https or the reverse based on the TargetUriScheme property. It also includes properties for specifying port numbers.
I have chosen to use an #if DEBUG block in my constructor to set my local development ports when I am building under the Debug configuration. This works for me since I always build under release when deploying, in which case the port numbers will default to null and will be left out of the url's.
The port numbers can also be set when applying the attribute to an action method. I could also see hooking these to a config file or some other configuration source to determine the port numbers at runtime.
public class ToggleHttpHttpsAttribute : FilterAttribute, IAuthorizationFilter
{
//supported uri scheme values
public enum UriScheme
{
Http,
Https
}
public ToggleHttpHttpsAttribute(
UriScheme uriScheme = UriScheme.Http)
{
TargetUriScheme = uriScheme;
#if DEBUG
//set DEBUG ports
HttpPort = 55892;
HttpsPort = 44301;
#endif
}
private UriScheme TargetUriScheme { get; set; }
public int? HttpPort { get; set; }
public int? HttpsPort { get; set; }
public void OnAuthorization(AuthorizationContext filterContext)
{
if(filterContext == null)
{
throw new ArgumentNullException("filterContext");
}
bool isHttps = filterContext.HttpContext.Request.IsSecureConnection;
if ((isHttps && TargetUriScheme == UriScheme.Http) || (!isHttps && TargetUriScheme == UriScheme.Https))
{
ToggleUriScheme(filterContext);
}
}
private void ToggleUriScheme(AuthorizationContext filterContext)
{
//only allow toggle if GET request
if (!string.Equals(filterContext.HttpContext.Request.HttpMethod, "GET", StringComparison.OrdinalIgnoreCase))
{
throw new InvalidOperationException("ToggleHttpHttpsAttribute can only be used on GET requests.");
}
filterContext.Result = GetRedirectResult(filterContext);
}
private RedirectResult GetRedirectResult(AuthorizationContext filterContext)
{
string prefix = TargetUriScheme == UriScheme.Http
? "http://"
: "https://";
int? port = TargetUriScheme == UriScheme.Http
? HttpPort
: HttpsPort;
string url = string.Format(
"{0}{1}{2}{3}",
prefix,
filterContext.HttpContext.Request.Url.Host,
port == null ? "" : string.Format(":{0}", port),
filterContext.HttpContext.Request.RawUrl);
return new RedirectResult(url);
}
}
I'm quite puzzled as to why I have had to spend hours trying to get this right. I did a URL rewrite similar to the way Scott Hanselman explains it here:
http://www.hanselman.com/blog/WorkingWithSSLAtDevelopmentTimeIsEasierWithIISExpress.aspx
This works perfectly and I just take out the rewrite rule on my production web server.