Summary
A WCF service hosted in IIS has only 1 method that takes 1 second to complete (Thread.Sleep in sample code). When 5 'clients' hammer the server with requests average response times is 1 second, 10 clients - around 2 seconds and with 20 clients the performance drops below the floor. I have tried all kind of settings and nothing helps.
I think the problem is in WCF (MS tried to make it fail-safe and put a lot of safeguards from developers): it tries to use as few threads as possible and thus the performance suffer.
Detailed description
Code
I have a very simple WCF service hosted in IIS:
using System.IO;
using System.ServiceModel;
using System.ServiceModel.Web;
using System.Threading;
namespace WCFPerf
{
[ServiceContract]
public interface IService1
{
[OperationContract]
[WebInvoke(Method = "POST",
ResponseFormat = WebMessageFormat.Xml,
RequestFormat = WebMessageFormat.Xml,
BodyStyle = WebMessageBodyStyle.Bare,
UriTemplate = "Test")]
Stream DoWork(Stream s);
}
public class Service1 : IService1
{
public Stream DoWork(Stream s)
{
Thread.Sleep(1000); // simulate work
return s;
}
}
}
The configuration file:
<configuration>
<system.web>
<compilation debug="true" targetFramework="4.0" />
<httpRuntime />
</system.web>
<system.serviceModel>
<services>
<service behaviorConfiguration="ServiceBehavior" name="WCFPerf.Service1">
<endpoint address="" behaviorConfiguration="web" binding="customBinding" bindingConfiguration="" contract="WCFPerf.IService1" />
<host>
<baseAddresses>
<add baseAddress="http://localhost:57676/Service1.svc" />
</baseAddresses>
</host>
</service>
</services>
<bindings>
<customBinding>
<binding closeTimeout="00:01:00" openTimeout="00:01:00" receiveTimeout="00:10:00" sendTimeout="00:01:00">
<webMessageEncoding />
<httpTransport manualAddressing="true" />
</binding>
</customBinding>
</bindings>
<behaviors>
<endpointBehaviors>
<behavior name="web">
<webHttp />
</behavior>
</endpointBehaviors>
<serviceBehaviors>
<behavior name="ServiceBehavior">
<serviceMetadata httpGetEnabled="true" />
<serviceDebug includeExceptionDetailInFaults="true" />
<serviceThrottling maxConcurrentCalls="200" maxConcurrentSessions="200" maxConcurrentInstances="200" />
</behavior>
</serviceBehaviors>
</behaviors>
</system.serviceModel>
<system.net>
<connectionManagement>
<add address="*" maxconnection="2000" />
</connectionManagement>
</system.net>
</configuration>
The test suit:
void Main()
{
var numberOfThreads = new[] {5, 10, 20, 30};
var table = new List<ResultInfo>();
foreach (var threadNumber in numberOfThreads)
{
var tasks = new List<Task<List<double>>>();
for (var i = 0; i < threadNumber; i++)
{
tasks.Add(Task<List<double>>.Factory.StartNew(() =>
{
var results = new List<double>();
for (var j = 0; j < 5; j++)
{
results.Add(SendRequest());
}
return results;
}));
}
Task.WaitAll(tasks.ToArray());
var allResults = tasks.SelectMany(t => t.Result);
table.Add(new ResultInfo{ Threads = threadNumber, Avg = allResults.Average().ToString("F2"), Min = allResults.Min().ToString("F2"), Max = allResults.Max().ToString("F2") });
}
table.Dump();
}
public double SendRequest()
{
var stopwatch = new Stopwatch();
using (var client = new HttpClient())
{
client.BaseAddress = new Uri(#"http://localhost:8081/");
stopwatch.Start();
var t = client.PostAsync("WCFPerf/Service1.svc/Test", new StringContent("123")).Result;
stopwatch.Stop();
return stopwatch.Elapsed.TotalSeconds;
}
}
public class ResultInfo
{
public int Threads {get;set;}
public string Avg {get;set;}
public string Min {get;set;}
public string Max {get;set;}
}
The test results are very inpredictable, but always there is a bad number:
This run I changed execution time from 1 second to 5 seconds just to demonstrate that the diff (2x times, 3x times, etc.) is relative, not absolute. Also notice that 'warm up' helps, but just a little bit.
Comments
During all my testing (and I did about 100 runs of different kind) the maximum amount of threads reported by Windows Task Manager in w3wp process was 54. I am using Core i3 CPU with 2 physical cores and 4 logical cores, 12 Gb RAM, running under Windows 8.1 and .Net 4.5. Memory print of the process always grows, but very slowly, the highest value was around 110 Mb.
dotTrace reports that about 7% of time is spent in my code and everything else - in system code.
I have tried doing the following:
In C:\Windows\Microsoft.NET\Framework\v4.0.30319\Aspnet.config:
<system.web>
<applicationPool maxConcurrentRequestsPerCPU="5000" maxConcurrentThreadsPerCPU="0" requestQueueLimit="5000"/>
</system.web>
In C:\Windows\Microsoft.NET\Framework\v4.0.30319\Config\machine.config
<system.net>
<connectionManagement>
<add address = "*" maxconnection = "400" />
</connectionManagement>
</system.net>
<system.web>
<processModel autoConfig="false" maxWorkerThreads="80" maxIoThreads="80" />
<httpRuntime minFreeThreads="10" minLocalRequestFreeThreads="10" />
Links: Web Settings Schema, Element, applicationPool maxConcurrentRequestsPerCPU, Element, Thread Throttling in IIS-hosted WCF, processModel Element, httpRuntime Element, ASP Registry Entries (IIS 6.0)
Ideas
My rationale (how it is supposed to work): while we have enough memory for every IIS/WCF creates a new thread for every request. For example when we have 40 'clients' connecting to the server - there will be about 45 threads in w3wp process (40 working threads and a few others for good measure). Because all my 'clients' wait for response before sending next request, there might be a little difference in response times (like 20-30%, not 2x or even 10x I see now).
The problems (as I see them):
There are not enough threads (only 54 threads for 70 clients)
There is some kind of queue, because already at 20 'clients' the average response time is at least twice longer than the actual logic
There is a non deterministic behavior, because results differ greatly from run to run
I have found an answer in kb aritcle number 2538826, the sample code also can be found in this blog post (with some additional information like how to add perf counters and write load test).
This solution will help you only if you have the described behavior (check provided links for details): number of threads you use (Thread count perf counter) does not scale as fast as your requests. The picture from KB article:
So the fix is to use some other way to create threads... and what is a better way than yo use a ThreadPool?
You need these two classes:
public class WorkerThreadPoolSynchronizer : SynchronizationContext
{
public override void Post(SendOrPostCallback d, object state)
{
// WCF almost always uses Post
ThreadPool.QueueUserWorkItem(new WaitCallback(d), state);
}
public override void Send(SendOrPostCallback d, object state)
{
// Only the peer channel in WCF uses Send
d(state);
}
}
and
[AttributeUsage(AttributeTargets.Class)]
public class WorkerThreadPoolBehaviorAttribute : Attribute, IContractBehavior
{
private static WorkerThreadPoolSynchronizer synchronizer = new WorkerThreadPoolSynchronizer();
void IContractBehavior.AddBindingParameters(
ContractDescription contractDescription,
ServiceEndpoint endpoint,
BindingParameterCollection bindingParameters)
{
}
void IContractBehavior.ApplyClientBehavior(
ContractDescription contractDescription,
ServiceEndpoint endpoint,
ClientRuntime clientRuntime)
{
}
void IContractBehavior.ApplyDispatchBehavior(
ContractDescription contractDescription,
ServiceEndpoint endpoint,
DispatchRuntime dispatchRuntime)
{
dispatchRuntime.SynchronizationContext = synchronizer;
}
void IContractBehavior.Validate(
ContractDescription contractDescription,
ServiceEndpoint endpoint)
{
}
}
And then apply the attribute to your service:
[WorkerThreadPoolBehavior] // this is what changed
public class Service1 : IService1
{
public Stream DoWork(Stream s)
{
Thread.Sleep(1000); // simulate work
return s;
}
}
Related
I want to get reminders for some Appointments that I have saved in database and they have a notificationTime property witch is the time when a notification needs to be displayed.
My approach so far is to write some kind of job that runs 1 or 2 times a day to pull the notifications that need to be registered in the next 24h and ofc register them (if you guys have a better ideea lmk :D )
This works BUT:
Only if the app is in foreground / background; then I get notification every 15min or so;
If I KILL the app I don't receive notification on my physical device (Xiaomi Redmi Note 9 Pro with Android version 12 SKQ),
only on the virtual one (Pixel 5 Android 13)
Right now I have a class that extends JobService and I use JobScheduler to schedule the Job to run every 15 min (for testing so I don't need to w8 12h xD )
Here is the JobScheduler witch I call in MainActivity file in OnCreate method
Console.WriteLine("Schedualing job");
TimeSpan interval = TimeSpan.FromMinutes(15);
var javaClass = Java.Lang.Class.FromType(typeof(NotificationService));
var componentName = new ComponentName(Application.Context, javaClass);
var jobInfo = new JobInfo.Builder(1, componentName)
.SetPeriodic(15 * 60 * 1000, 30 * 60 * 1000)
.SetRequiredNetworkType(NetworkType.Any)
.SetPersisted(true)
.Build();
var jobScheduler = (JobScheduler)GetSystemService(JobSchedulerService);
var resultCode = jobScheduler.Schedule(jobInfo);
and here is the NotificationService.cs
[Service(Name = "com.companyname.deratizare_mobile.NotificationService",
Permission = "android.permission.BIND_JOB_SERVICE")]
public class NotificationService : JobService
{
public override StartCommandResult OnStartCommand(Intent intent, StartCommandFlags flags, int startId)
{
return StartCommandResult.StickyCompatibility;
}
public override bool OnStartJob(JobParameters #params)
{
Console.WriteLine("Job started");
Task.Run(async () =>
{
//var hasSuccessful = await ProccessNotificationToRegister();
var notification = new NotificationRequest
{
Title = "Job",
Description = $"Description",
Schedule = new NotificationRequestSchedule
{
NotifyTime = DateTime.Now,
}
};
LocalNotificationCenter.Current.Show(notification);
JobFinished(#params, false);
Console.WriteLine("Job finished");
});
return true;
}
public override bool OnStopJob(JobParameters #params)
{
Console.WriteLine("Job stopped");
return true;
}
}
AndroidManifest
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.ACCESS_NOTIFICATION_POLICY" />
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.BIND_JOB_SERVICE" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
UPDATE
I have given permission to auto start to my app from the device and it works
SOLUTION
I ended up using FCM and a hosted service on the server that checks the cache every 5 minutes where I have stored the next notification that needs to be displaied
You can try to use Firebase push notifications.
With push notifications, you can update your users without requiring the app to run at all times or having it poll a server, potentially running down the battery.
For more information, you can check Implementing Push Notifications in Your Android Apps and Firebase Cloud Messaging.
I have a connected WCF service, where the client configuration code is as below:
var method = typeof(XmlSerializer).GetMethod("set_Mode", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static);
method.Invoke(null, new object[] { 1 });
BasicHttpsBinding httpBd = new BasicHttpsBinding(BasicHttpsSecurityMode.Transport);
httpBd.MaxReceivedMessageSize = Int32.MaxValue;
httpBd.MaxBufferSize = Int32.MaxValue;
var client = new FindServicePortTypeClient(httpBd,
new EndpointAddress(_settings.Url));
var bd = client.Endpoint.Binding as BasicHttpsBinding;
bd.Security.Mode = BasicHttpsSecurityMode.Transport;
bd.Security.Transport.ClientCredentialType = HttpClientCredentialType.Basic;
client.ClientCredentials.UserName.UserName = _settings.User;
client.ClientCredentials.UserName.Password = _settings.Password;
On the service I have to configure some performance metrics like "relation deepness". How can I achieve this
In WCF, you can use ServiceThrottlingBehavior to control WCF service performance.Using this behavior, you can fine-tune the performance of your Windows Communication Foundation application.
You can configure the values of these properties in the configuration file.
<behaviors>
<serviceBehaviors>
<behavior name="Throttled">
<serviceThrottling
maxConcurrentCalls="1"
maxConcurrentSessions="1"
maxConcurrentInstances="1"
/>
<serviceMetadata
httpGetEnabled="true"
httpGetUrl=""
/>
</behavior>
</serviceBehaviors>
</behaviors>
MaxConcurrentSessions:The maximum number of sessions a service host accepts. The default is 100 times the processor count.
MaxConcurrentCalls:The upper limit of active messages in the service. The default is 16 times the processor count.
MaxConcurrentInstances:The maximum number of InstanceContext objects in the service at one time. The default is the sum of the value of MaxConcurrentSessions and the value of MaxConcurrentCalls.
Windows Communication Foundation includes a large set of performance counters to help you gauge your application's performance.For more information about Performance Counters,Please refer to the following link:
https://learn.microsoft.com/en-us/dotnet/framework/wcf/diagnostics/performance-counters/
I'm using DiskCryptor for the first time and i badly need an .EXE that i can use with a hotkey to execute the mounting of my encrypted partitions.
The encrypted drives are just my data drives/partitions and not my OS partition (Windows 7), and they all share the same encryption.
My current .exe is just a batch cmd file which mounts all drives and loads a keyfile from a pre-defined location, which is the root of an encrypted USB key, designated J:\
However i would really like to add a password that i have to manually enter without it being stored anywhere in a cache or remembered by any means, but the dcrypt software doesn't seem to have a built in prompt function, a small window or cmd prompt where i can enter my password.
I have a macro hotkey that launches two other hotkeys which locks the system using Screenblur and unmounts all drives using dccon.exe on an unencrypted partition. And the cmd below which mounts all drives if the encrypted usb drive with the keyfile and dcrypt is inserted into my pc.
J:\dcrypt\dccon.exe" -mountall -kf J:\key -p ""
I would like the .EXE to prompt for input which is to be inserted between the ""
And if possible, if the "J:\key" file is not found, prompt a window that lets me browse to find the correct file manually (preferable a window with only an input field and enter button).
I'm not a programmer, so i'm really looking for help achieving this..
From what i get it's actually extremely simple, and if anyone got the know-how maybe you might be as kind as writing the few lines of code needed for me, and posting the code and how to compile correctly?
Would really appreciate this, many thanks in advance to any kind soul out there!
Kindly
-st0rm
i was in that mood ...
so this is beerware ... feel free to buy me a beer if we ever meet ...
c#
app.config:
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<configSections>
<sectionGroup name="applicationSettings" type="System.Configuration.ApplicationSettingsGroup, System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" >
<section name="WindowsFormsApplication2.Properties.Settings" type="System.Configuration.ClientSettingsSection, System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" requirePermission="false" />
</sectionGroup>
</configSections>
<applicationSettings>
<WindowsFormsApplication2.Properties.Settings>
<setting name="path_to_dccon_exe" serializeAs="String">
<value>J:\dcrypt\dccon.exe</value>
</setting>
<setting name="parameters_for_dccon_exe" serializeAs="String">
<value>-mountall -kf J:\key -p "{0}"</value>
</setting>
</WindowsFormsApplication2.Properties.Settings>
</applicationSettings>
</configuration>
Form1.cs
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
namespace WindowsFormsApplication2
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void button1_Click(object sender, EventArgs e)
{
try
{
System.Diagnostics.Process.Start(Properties.Settings.Default.path_to_dccon_exe, string.Format(Properties.Settings.Default.parameters_for_dccon_exe, textBox1.Text));
}
catch (Exception ex)
{
MessageBox.Show(ex.Message + "\n\n" + ex.StackTrace, "Exception");
}
}
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.IContainer components = null;
/// <summary>
/// Clean up any resources being used.
/// </summary>
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}
#region Windows Form Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent()
{
this.button1 = new System.Windows.Forms.Button();
this.textBox1 = new System.Windows.Forms.TextBox();
this.SuspendLayout();
//
// button1
//
this.button1.Location = new System.Drawing.Point(152, 10);
this.button1.Name = "button1";
this.button1.Size = new System.Drawing.Size(75, 23);
this.button1.TabIndex = 1;
this.button1.Text = "OK";
this.button1.UseVisualStyleBackColor = true;
this.button1.Click += new System.EventHandler(this.button1_Click);
//
// textBox1
//
this.textBox1.Location = new System.Drawing.Point(46, 12);
this.textBox1.Name = "textBox1";
this.textBox1.PasswordChar = '*';
this.textBox1.Size = new System.Drawing.Size(100, 20);
this.textBox1.TabIndex = 2;
//
// Form1
//
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.ClientSize = new System.Drawing.Size(272, 42);
this.Controls.Add(this.textBox1);
this.Controls.Add(this.button1);
this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedDialog;
this.Name = "Form1";
this.Text = "unsuspicious Application";
this.ResumeLayout(false);
this.PerformLayout();
}
#endregion
private System.Windows.Forms.Button button1;
private System.Windows.Forms.TextBox textBox1;
}
}
edit: so ... this is what i wrote ... ok, in addition to throwing 2 controls on a from and arranging them
try
{
System.Diagnostics.Process.Start(Properties.Settings.Default.path_to_dccon_exe, string.Format(Properties.Settings.Default.parameters_for_dccon_exe, textBox1.Text));
}
catch (Exception ex)
{
MessageBox.Show(ex.Message + "\n\n" + ex.StackTrace, "Exception");
}
I have the following requirements
Set a value in web.config and enable maintenance mode
All non-ajax requests should be shown a custom error page, with the http status code set to 503. The url of page should be retained.
All ajax requests should be responded with http status code 503
I should have an opportunity to do some basic logging to a file. Log the url and the user Identity if he happened to be logged into the app
I am using ELMAH to track/log all unhandled exceptions. The mechanism for implementing maintenance mode shouldn't need me to not use ELMAH
I have "runAllManagedModulesForAllRequests" set to true. this was originally done for use with RequestReduce. we no longer use it, but I am hesitant to reset its value to false. I am not sure if any other library needs it.
Once I realized there is nothing built in which supports the above requirements, I felt I had the following two options in front of me (App_offile.html won't work for me).
an HttpModule
an MVC ActionFilter
I dropped the MVC ActionFilter as I couldn't figure out how to guarantee it to run before any authentication/authorization filters. I have a custom authentication filter which hits the db. The idea behind the maintenance mode is the db might be offline, yet the web-app shouldn't show a 500 custom error page, but a 503 custom error page.
I wrote the following httpmodule and added in my web.config. It works for ajax requests. It kinda works for non-ajax requests. All requests get redirected to the 503 error page. The side-effect is all requests for static content also result in a 503. My error page thus is shown unstyled :(
// the http module
public class MaintenanceModeModule : IHttpModule
{
private static bool _isUnderMaintenance;
static MaintenanceModeModule()
{
var valueStr = (ConfigurationManager.AppSettings["UnderMaintenance"] ?? (false).ToString());
bool underMaintenance;
bool.TryParse(valueStr, out underMaintenance);
_isUnderMaintenance = underMaintenance;
}
public void Init(HttpApplication application)
{
application.BeginRequest += OnBeginRequest;
}
private void OnBeginRequest(object sender, EventArgs e)
{
var application = (HttpApplication) sender;
var request = application.Request;
var response = application.Response;
if (_isUnderMaintenance == false)
{
return;
}
application.Context.Items["under_maintenance"] = true; // used later
if (request.Url.PathAndQuery == "/503") // the url of the action that renders the custom error page
{
return;
}
const int statusCode = (int) HttpStatusCode.ServiceUnavailable;
const string statusMessage = "Temporarily down for maintenance";
var requestWrapper = new HttpRequestWrapper(request);
if (requestWrapper.IsAjaxRequest())
{
response.Clear();
response.ClearContent();
response.ClearHeaders();
response.StatusCode = statusCode;
response.TrySkipIisCustomErrors = true;
response.StatusDescription = statusMessage;
response.End();
return;
}
// doesn't work, shows the Yellow Screen of Death (YSoD)
// application.Context.Server.Transfer("~/503", preserveForm: true);
// doesn't work, shows the Yellow Screen of Death (YSoD)
// throw new HttpException(statusCode, statusMessage);
response.Redirect("~/503");
}
public void Dispose()
{
}
}
...
// web.config
// only the relevant portions of each section is shown
<appSettings>
<add key="UnderMaintenance" value="true" />
</appSettings>
<customErrors mode="On"> <!-- Custom errors are on, even then I was seeing YSoDs during my attempts -->
<error statusCode="404" redirect="404" />
<error statusCode="503" redirect="503" />
</customErrors>
<system.webServer>
<httpErrors existingResponse="PassThrough">
</httpErrors>
<modules runAllManagedModulesForAllRequests="true">
<add name="MaintenanceMode" type="WebApp.Code.MvcInfrastructure.MaintenanceModeModule" />
</modules>
</system.webServer>
...
// route config
routes.MapRoute("503Error", "503", new { controller = "Error", action = "UnderMaintenance" });
...
// error controller
// the authentication filter skips authentication if the allowanonymous attribute is present
[AllowAnonymous]
public class ErrorController : CustomBaseController
{
public ErrorController(AppConfig appConfig)
: base(appConfig)
{
}
public ActionResult UnderMaintenance()
{
// behind the scenes reads the value from HttpContext.Items.
// This was set during the execution of the httpmodule
if (AppConfig.UnderMaintenance == false)
{
return new RedirectResult("~/");
}
Response.StatusCode = (int) HttpStatusCode.ServiceUnavailable;
Response.TrySkipIisCustomErrors = true;
// the actual content of the view is not relevant now
return View("Error503");
}
}
The problems with this approach,
Each non-ajax request is responded with a 302 and then a 503
The URL requested by the browser is not retained
It returns a 503 for all static assets as well
The code I wrote and web.config settings I enabled are all cobbled together from various sources. I am not fully sure what those settings do or what the recommended way is. Please feel free to answer with a completely different method, as long as it can meet the requirements stated.
I'm experimenting with "configuration-less WIF", where I want to accept a SAML2 token that is generated by Windows Azure's AppFabric STS.
What I'm doing is parsing checking the current request for token information, like so:
if (Request.Form.Get(WSFederationConstants.Parameters.Result) != null)
{
SignInResponseMessage message =
WSFederationMessage.CreateFromFormPost(System.Web.HttpContext.Current.Request) as SignInResponseMessage;
var securityTokenHandlers = SecurityTokenHandlerCollection.CreateDefaultSecurityTokenHandlerCollection();
XmlTextReader xmlReader = new XmlTextReader(
new StringReader(message.Result));
SecurityToken token = securityTokenHandlers.ReadToken(xmlReader);
if (token != null)
{
ClaimsIdentityCollection claims = securityTokenHandlers.ValidateToken(token);
IPrincipal principal = new ClaimsPrincipal(claims);
}
}
The code above uses the SecurityTokenHandlerCollection.CreateDefaultSecurityTokenHandlerCollection(); colection to verify and handle the SAML token. However: this does not work because obviously the application has not bee nconfigured correctly. How would I specify the follwing configuration from XML programmaticaly on my securityTokenHandlers collection?
<microsoft.identityModel>
<service>
<audienceUris>
<add value="http://www.someapp.net/" />
</audienceUris>
<federatedAuthentication>
<wsFederation passiveRedirectEnabled="true" issuer="https://rd-test.accesscontrol.appfabriclabs.com/v2/wsfederation" realm="http://www.thisapp.net" requireHttps="false" />
<cookieHandler requireSsl="false" />
</federatedAuthentication>
<applicationService>
<claimTypeRequired>
<claimType type="http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name" optional="true" />
<claimType type="http://schemas.microsoft.com/ws/2008/06/identity/claims/role" optional="true" />
</claimTypeRequired>
</applicationService>
<issuerNameRegistry type="Microsoft.IdentityModel.Tokens.ConfigurationBasedIssuerNameRegistry, Microsoft.IdentityModel, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35">
<trustedIssuers>
<add thumbprint="XYZ123" name="https://somenamespace.accesscontrol.appfabriclabs.com/" />
</trustedIssuers>
</issuerNameRegistry>
</service>
I was struggling with the same and found a working solution in WIF 3.5/4.0. Since maartenba's link seems to be dead, I wanted to post my solution here.
Our requirements were:
Configuration fully in code (as we ship a default web.config with the app)
Maximum allowed .Net version 4.0 (hence I am using WIF 3.5/4.0)
What I used to arrive at the solution:
Information about dynamic WIF configuration provided by Daniel Wu
here.
This
method
to register HTTP modules at runtime, explained by David Ebbo. I
also tried the more elegant method explained by Rick
Strahl,
but that unfortunately did not do the trick for me.
Edit 2016/09/02: instead of adding a separate "pre application start
code" class as in David Ebbo's example, the WIF-related HTTP modules
can also be registered in the static constructor of the
`HttpApplication' class. I have adapted the code to this somewhat
cleaner solution.
My solution needs nothing in web.config. The bulk of the code is in global.asax.cs. Configuration is hard-coded in this sample:
using System;
using System.IdentityModel.Selectors;
using System.Security.Cryptography.X509Certificates;
using System.Web;
using Microsoft.IdentityModel.Tokens;
using Microsoft.IdentityModel.Web;
namespace TestADFS
{
public class SessionAuthenticationModule : Microsoft.IdentityModel.Web.SessionAuthenticationModule
{
protected override void InitializePropertiesFromConfiguration(string serviceName)
{
}
}
public class WSFederationAuthenticationModule : Microsoft.IdentityModel.Web.WSFederationAuthenticationModule
{
protected override void InitializePropertiesFromConfiguration(string serviceName)
{
ServiceConfiguration = FederatedAuthentication.ServiceConfiguration;
PassiveRedirectEnabled = true;
RequireHttps = true;
Issuer = "https://nl-joinadfstest.joinadfstest.local/adfs/ls/";
Realm = "https://67px95j.decos.com/testadfs";
}
}
public class Global : HttpApplication
{
static Global()
{
Microsoft.Web.Infrastructure.DynamicModuleHelper.DynamicModuleUtility.RegisterModule(typeof(SessionAuthenticationModule));
Microsoft.Web.Infrastructure.DynamicModuleHelper.DynamicModuleUtility.RegisterModule(typeof(WSFederationAuthenticationModule));
}
protected void Application_Start(object sender, EventArgs e)
{
FederatedAuthentication.ServiceConfigurationCreated += FederatedAuthentication_ServiceConfigurationCreated;
}
internal void FederatedAuthentication_ServiceConfigurationCreated(object sender, Microsoft.IdentityModel.Web.Configuration.ServiceConfigurationCreatedEventArgs e)
{
X509Store store = new X509Store(StoreName.My, StoreLocation.LocalMachine);
store.Open(OpenFlags.ReadOnly);
X509Certificate2Collection coll = store.Certificates.Find(X509FindType.FindByThumbprint, "245537E9BB2C086D3C880982FA86267FBD66B9A3", false);
if (coll.Count > 0)
e.ServiceConfiguration.ServiceCertificate = coll[0];
store.Close();
AudienceRestriction ar = new AudienceRestriction(AudienceUriMode.Always);
ar.AllowedAudienceUris.Add(new Uri("https://67px95j.decos.com/testadfs"));
e.ServiceConfiguration.AudienceRestriction = ar;
ConfigurationBasedIssuerNameRegistry inr = new ConfigurationBasedIssuerNameRegistry();
inr.AddTrustedIssuer("6C9B96D90257B65B6F181C2478D869473DC359EA", "http://NL-JOINADFSTEST.joinadfstest.local/adfs/services/trust");
e.ServiceConfiguration.IssuerNameRegistry = inr;
e.ServiceConfiguration.CertificateValidationMode = System.ServiceModel.Security.X509CertificateValidationMode.None;
}
protected void Application_AuthenticateRequest(object sender, EventArgs e)
{
FederatedAuthentication.WSFederationAuthenticationModule.ServiceConfiguration = FederatedAuthentication.ServiceConfiguration;
}
}
}
Usage
My app is asp.net WebForms, running in classic pipeline mode and supports forms authentication as well as ADFS login. Because of that, authentication is handled in a common base class shared by all .aspx pages:
protected override void OnInit(EventArgs e)
{
if (NeedsAuthentication && !User.Identity.IsAuthenticated)
{
SignInRequestMessage sirm = new SignInRequestMessage(
new Uri("https://nl-joinadfstest.joinadfstest.local/adfs/ls/"),
ApplicationRootUrl)
{
Context = ApplicationRootUrl,
HomeRealm = ApplicationRootUrl
};
Response.Redirect(sirm.WriteQueryString());
}
base.OnInit(e);
}
In this code, ApplicationRootUrl is the application path ending in "/" (the "/" is important in Classic pipeline mode).
As a stable implementation for logout in mixed mode was not so easy, I want to show the code for that as well. Technically it works, but I still have an issue with IE immediately logging in again after logging out an ADFS account:
if (User.Identity.IsAuthenticated)
{
if (User.Identity.AuthenticationType == "Forms")
{
FormsAuthentication.SignOut();
Session.Clear();
Session.Abandon();
ResetCookie(FormsAuthentication.FormsCookieName);
ResetCookie("ASP.NET_SessionId");
Response.Redirect(ApplicationRootUrl + "Default.aspx");
HttpContext.Current.ApplicationInstance.CompleteRequest();
}
else
{
FederatedAuthentication.SessionAuthenticationModule.SignOut();
FederatedAuthentication.SessionAuthenticationModule.DeleteSessionTokenCookie();
Uri uri = new Uri(ApplicationRootUrl + "Default.aspx");
WSFederationAuthenticationModule.FederatedSignOut(
new Uri("https://nl-joinadfstest.joinadfstest.local/adfs/ls/"),
uri); // 1st url is single logout service binding from adfs metadata
}
}
(ResetCookie is a helper function that clears a response cookie and sets its expiration in the past)
Just a thought, no idea whether this works: Isn't there a way to get at the actual XML (which is empty in your case) and modify it at runtime through the classes in Microsoft.IdentityModel.Configuration?
Alternatively, some of the things in the XML you can modify at the time the sign-in request is sent out, in the RedirectingToIdentityProvider event by modifying the SignInRequestMessage
FYI: found a solution and implemented it in a module described (and linked) here: http://blog.maartenballiauw.be/post/2011/02/14/Authenticate-Orchard-users-with-AppFabric-Access-Control-Service.aspx