├── .gitignore ├── AspNetCoreService.cs ├── Cache.cs ├── LICENSE ├── LargeItem.cs ├── MessagePackMediaTypeFormatter.cs ├── Program.cs ├── README.md ├── RestServiceBase.cs ├── SerializerType.cs ├── SmallItem.cs ├── Utf8JsonMediaTypeFormatter.cs ├── WcfService.cs ├── WcfVsWebApiVsAspNetCoreBenchmark.csproj ├── WcfVsWebApiVsAspNetCoreBenchmark.sln └── WebApiService.cs /.gitignore: -------------------------------------------------------------------------------- 1 | obj/ 2 | bin/ 3 | BenchmarkDotNet.Artifacts/ 4 | .idea/ 5 | *.user 6 | .vs/ 7 | *.log 8 | -------------------------------------------------------------------------------- /AspNetCoreService.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using System.Threading.Tasks; 4 | 5 | using MessagePack; 6 | 7 | using Microsoft.AspNetCore.Builder; 8 | using Microsoft.AspNetCore.Hosting; 9 | using Microsoft.AspNetCore.Mvc; 10 | using Microsoft.AspNetCore.Mvc.Formatters; 11 | using Microsoft.Extensions.DependencyInjection; 12 | 13 | namespace WcfVsWebApiVsAspNetCoreBenchmark 14 | { 15 | public class AspNetCoreService: RestServiceBase 16 | where T: class, new() 17 | { 18 | public void Start() 19 | { 20 | var startup = new AspNetCoreStartup(_format); 21 | 22 | _host = new WebHostBuilder() 23 | .UseKestrel(opt => opt.Limits.MaxRequestBodySize = 180000000) 24 | .ConfigureServices(startup.ConfigureServices) 25 | .Configure(startup.Configure) 26 | .UseUrls($"http://localhost:{_port}") 27 | .Build(); 28 | _host.Start(); 29 | 30 | InitializeClients(); 31 | } 32 | 33 | public void Stop() 34 | { 35 | _host?.StopAsync().Wait(); 36 | } 37 | 38 | public AspNetCoreService(int port, SerializerType format, int itemCount) 39 | : base(port, format, itemCount) 40 | { 41 | _port = port; 42 | _format = format; 43 | } 44 | 45 | private readonly int _port; 46 | private readonly SerializerType _format; 47 | private IWebHost _host; 48 | } 49 | 50 | public class AspNetCoreStartup 51 | { 52 | public void ConfigureServices(IServiceCollection services) 53 | { 54 | services.AddMvc(opt => 55 | { 56 | opt.InputFormatters.RemoveType(); 57 | 58 | var jsonInputFormatter = opt.InputFormatters.OfType().First(); 59 | var jsonOutputFormatter = opt.OutputFormatters.OfType().First(); 60 | 61 | opt.InputFormatters.Clear(); 62 | opt.OutputFormatters.Clear(); 63 | 64 | switch(_format) 65 | { 66 | case SerializerType.Xml: 67 | opt.InputFormatters.Add(new XmlSerializerInputFormatter()); 68 | opt.OutputFormatters.Add(new XmlSerializerOutputFormatter()); 69 | break; 70 | case SerializerType.JsonNet: 71 | opt.InputFormatters.Add(jsonInputFormatter); 72 | opt.OutputFormatters.Add(jsonOutputFormatter); 73 | break; 74 | case SerializerType.MessagePack: 75 | opt.InputFormatters.Add(new MessagePackInputFormatter()); 76 | opt.OutputFormatters.Add(new MessagePackOutputFormatter()); 77 | break; 78 | case SerializerType.Utf8Json: 79 | opt.InputFormatters.Add(new Utf8Json.AspNetCoreMvcFormatter.JsonInputFormatter()); 80 | opt.OutputFormatters.Add(new Utf8Json.AspNetCoreMvcFormatter.JsonOutputFormatter()); 81 | break; 82 | default: 83 | throw new ArgumentOutOfRangeException(); 84 | } 85 | }); 86 | } 87 | 88 | public void Configure(IApplicationBuilder app) 89 | { 90 | app.UseMvc(); 91 | } 92 | 93 | public AspNetCoreStartup(SerializerType format) 94 | { 95 | _format = format; 96 | } 97 | 98 | private readonly SerializerType _format; 99 | } 100 | 101 | public class AspNetCoreController: Controller 102 | { 103 | [HttpPost("api/operation/SmallItem")] 104 | public SmallItem[] Operation([FromBody] SmallItem[] items, int itemCount) 105 | { 106 | return Cache.SmallItems.Take(itemCount).ToArray(); 107 | } 108 | 109 | [HttpPost("api/operation/LargeItem")] 110 | public LargeItem[] Operation([FromBody] LargeItem[] items, int itemCount) 111 | { 112 | return Cache.LargeItems.Take(itemCount).ToArray(); 113 | } 114 | } 115 | 116 | public class MessagePackInputFormatter: InputFormatter 117 | { 118 | public override async Task ReadRequestBodyAsync(InputFormatterContext context) 119 | { 120 | var items = await MessagePackSerializer.DeserializeAsync(context.HttpContext.Request.Body); 121 | 122 | return InputFormatterResult.Success(items); 123 | } 124 | 125 | public MessagePackInputFormatter() 126 | { 127 | SupportedMediaTypes.Clear(); 128 | SupportedMediaTypes.Add("application/x-msgpack"); 129 | } 130 | } 131 | 132 | public class MessagePackOutputFormatter: OutputFormatter 133 | { 134 | public override Task WriteResponseBodyAsync(OutputFormatterWriteContext context) 135 | { 136 | return MessagePackSerializer.SerializeAsync(context.HttpContext.Response.Body, context.Object); 137 | } 138 | 139 | public MessagePackOutputFormatter() 140 | { 141 | SupportedMediaTypes.Clear(); 142 | SupportedMediaTypes.Add("application/x-msgpack"); 143 | } 144 | } 145 | } 146 | -------------------------------------------------------------------------------- /Cache.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | 4 | using AutoFixture; 5 | 6 | namespace WcfVsWebApiVsAspNetCoreBenchmark 7 | { 8 | internal static class Cache 9 | { 10 | public static readonly SmallItem[] SmallItems = Enumerable.Range(0, 1000).Select(_ => new SmallItem { Id = Guid.NewGuid() }).ToArray(); 11 | public static readonly LargeItem[] LargeItems = new Fixture().CreateMany(count: 1000).ToArray(); 12 | 13 | public static T[] Get() 14 | { 15 | if(typeof(T) == typeof(SmallItem)) 16 | return (T[]) (object) SmallItems; 17 | if(typeof(T) == typeof(LargeItem)) 18 | return (T[]) (object) LargeItems; 19 | 20 | throw new InvalidOperationException(); 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Erik Heemskerk 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /LargeItem.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | using MessagePack; 5 | 6 | namespace WcfVsWebApiVsAspNetCoreBenchmark 7 | { 8 | [MessagePackObject] 9 | public class LargeItem 10 | { 11 | [Key(0)] 12 | public Guid OrderId { get; set; } 13 | 14 | [Key(1)] 15 | public ulong OrderNumber { get; set; } 16 | 17 | [Key(2)] 18 | public string EmailAddress { get; set; } 19 | 20 | [Key(3)] 21 | public Address ShippingAddress { get; set; } = new Address(); 22 | 23 | [Key(4)] 24 | public Address InvoiceAddress { get; set; } = new Address(); 25 | 26 | [Key(5)] 27 | public DateTimeOffset RequestedDeliveryDate { get; set; } 28 | 29 | [Key(6)] 30 | public decimal ShippingCosts { get; set; } 31 | 32 | [Key(7)] 33 | public DateTimeOffset LastModified { get; set; } 34 | 35 | [Key(8)] 36 | public Guid CreateNonce { get; set; } 37 | 38 | [Key(9)] 39 | public List OrderLines { get; set; } 40 | } 41 | 42 | [MessagePackObject] 43 | public class OrderLine 44 | { 45 | [Key(0)] 46 | public string Sku { get; set; } 47 | 48 | [Key(1)] 49 | public int Quantity { get; set; } 50 | 51 | [Key(2)] 52 | public string Product { get; set; } 53 | 54 | [Key(3)] 55 | public decimal Price { get; set; } 56 | } 57 | 58 | [MessagePackObject] 59 | public class Address 60 | { 61 | [Key(0)] 62 | public string Name { get; set; } 63 | 64 | [Key(1)] 65 | public string Street { get; set; } 66 | 67 | [Key(2)] 68 | public string HouseNumber { get; set; } 69 | 70 | [Key(3)] 71 | public string PostalCode { get; set; } 72 | 73 | [Key(4)] 74 | public string City { get; set; } 75 | 76 | [Key(5)] 77 | public string Country { get; set; } 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /MessagePackMediaTypeFormatter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Net; 4 | using System.Net.Http; 5 | using System.Net.Http.Formatting; 6 | using System.Net.Http.Headers; 7 | using System.Threading.Tasks; 8 | 9 | using MessagePack; 10 | 11 | namespace WcfVsWebApiVsAspNetCoreBenchmark 12 | { 13 | public class MessagePackMediaTypeFormatter: MediaTypeFormatter 14 | { 15 | public override bool CanReadType(Type type) 16 | { 17 | return true; 18 | } 19 | 20 | public override bool CanWriteType(Type type) 21 | { 22 | return true; 23 | } 24 | 25 | public override Task ReadFromStreamAsync(Type type, Stream readStream, HttpContent content, IFormatterLogger formatterLogger) 26 | { 27 | var obj = MessagePackSerializer.NonGeneric.Deserialize(type, readStream); 28 | 29 | return Task.FromResult(obj); 30 | } 31 | 32 | public override Task WriteToStreamAsync(Type type, object value, Stream writeStream, HttpContent content, TransportContext transportContext) 33 | { 34 | MessagePackSerializer.NonGeneric.Serialize(type, writeStream, value); 35 | return Task.CompletedTask; 36 | } 37 | 38 | public MessagePackMediaTypeFormatter() 39 | { 40 | SupportedMediaTypes.Add(new MediaTypeHeaderValue("application/x-msgpack")); 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /Program.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Threading.Tasks; 3 | 4 | using BenchmarkDotNet.Analysers; 5 | using BenchmarkDotNet.Attributes; 6 | using BenchmarkDotNet.Columns; 7 | using BenchmarkDotNet.Configs; 8 | using BenchmarkDotNet.Diagnosers; 9 | using BenchmarkDotNet.Exporters; 10 | using BenchmarkDotNet.Jobs; 11 | using BenchmarkDotNet.Loggers; 12 | using BenchmarkDotNet.Running; 13 | 14 | namespace WcfVsWebApiVsAspNetCoreBenchmark 15 | { 16 | class Program 17 | { 18 | static void Main() 19 | { 20 | BenchmarkRunner.Run(); 21 | } 22 | } 23 | 24 | [Config(typeof(Config))] 25 | public class WcfVsWebApiVsAspNetCoreMvc 26 | { 27 | private class Config: ManualConfig 28 | { 29 | public Config() 30 | { 31 | Add(Job.Clr); 32 | Add(ConsoleLogger.Default); 33 | Add(TargetMethodColumn.Method, new ParamColumn(nameof(ItemCount)), StatisticColumn.Mean, StatisticColumn.P95); 34 | Add(MemoryDiagnoser.Default.GetColumnProvider()); 35 | Add(EnvironmentAnalyser.Default); 36 | Add(MemoryDiagnoser.Default); 37 | Add(MarkdownExporter.Default); 38 | UnionRule = ConfigUnionRule.AlwaysUseLocal; 39 | } 40 | } 41 | 42 | [Params(0, 10, 100, 1000)] 43 | public int ItemCount { get; set; } 44 | 45 | [GlobalSetup] 46 | public void Initialize() 47 | { 48 | int port = 9001; 49 | 50 | _smallTextWcfService = new WcfService( 51 | port: port++, 52 | bindingType: WcfBindingType.BasicText, 53 | itemCount: ItemCount 54 | ); 55 | _smallTextWcfService.Start(); 56 | 57 | _smallWebXmlWcfService = new WcfService( 58 | port: port++, 59 | bindingType: WcfBindingType.WebXml, 60 | itemCount: ItemCount 61 | ); 62 | _smallWebXmlWcfService.Start(); 63 | 64 | _smallWebJsonWcfService = new WcfService( 65 | port: port++, 66 | bindingType: WcfBindingType.WebJson, 67 | itemCount: ItemCount 68 | ); 69 | _smallWebJsonWcfService.Start(); 70 | 71 | _smallWebApiJsonNetService = new WebApiService( 72 | port: port++, 73 | format: SerializerType.JsonNet, 74 | itemCount: ItemCount 75 | ); 76 | _smallWebApiJsonNetService.Start(); 77 | 78 | _smallWebApiMessagePackService = new WebApiService( 79 | port: port++, 80 | format: SerializerType.MessagePack, 81 | itemCount: ItemCount 82 | ); 83 | _smallWebApiMessagePackService.Start(); 84 | 85 | _smallWebApiXmlService = new WebApiService( 86 | port: port++, 87 | format: SerializerType.Xml, 88 | itemCount: ItemCount 89 | ); 90 | _smallWebApiXmlService.Start(); 91 | 92 | _smallWebApiUtf8JsonService = new WebApiService( 93 | port: port++, 94 | format: SerializerType.Utf8Json, 95 | itemCount: ItemCount 96 | ); 97 | _smallWebApiUtf8JsonService.Start(); 98 | 99 | _smallAspNetCoreJsonNetService = new AspNetCoreService( 100 | port: port++, 101 | format:SerializerType.JsonNet, 102 | itemCount: ItemCount 103 | ); 104 | _smallAspNetCoreJsonNetService.Start(); 105 | 106 | _smallAspNetCoreMessagePackService = new AspNetCoreService( 107 | port: port++, 108 | format: SerializerType.MessagePack, 109 | itemCount: ItemCount 110 | ); 111 | _smallAspNetCoreMessagePackService.Start(); 112 | 113 | _smallAspNetCoreXmlService = new AspNetCoreService( 114 | port: port++, 115 | format: SerializerType.Xml, 116 | itemCount: ItemCount 117 | ); 118 | _smallAspNetCoreXmlService.Start(); 119 | 120 | _smallAspNetCoreUtf8JsonService = new AspNetCoreService( 121 | port: port++, 122 | format: SerializerType.Utf8Json, 123 | itemCount: ItemCount 124 | ); 125 | _smallAspNetCoreUtf8JsonService.Start(); 126 | 127 | _largeTextWcfService = new WcfService( 128 | port: port++, 129 | bindingType: WcfBindingType.BasicText, 130 | itemCount: ItemCount 131 | ); 132 | _largeTextWcfService.Start(); 133 | 134 | _largeWebXmlWcfService = new WcfService( 135 | port: port++, 136 | bindingType: WcfBindingType.WebXml, 137 | itemCount: ItemCount 138 | ); 139 | _largeWebXmlWcfService.Start(); 140 | 141 | _largeWebJsonWcfService = new WcfService( 142 | port: port++, 143 | bindingType: WcfBindingType.WebJson, 144 | itemCount: ItemCount 145 | ); 146 | _largeWebJsonWcfService.Start(); 147 | 148 | _largeWebApiJsonNetService = new WebApiService( 149 | port: port++, 150 | format: SerializerType.JsonNet, 151 | itemCount: ItemCount 152 | ); 153 | _largeWebApiJsonNetService.Start(); 154 | 155 | _largeWebApiMessagePackService = new WebApiService( 156 | port: port++, 157 | format: SerializerType.MessagePack, 158 | itemCount: ItemCount 159 | ); 160 | _largeWebApiMessagePackService.Start(); 161 | 162 | _largeWebApiXmlService = new WebApiService( 163 | port: port++, 164 | format: SerializerType.Xml, 165 | itemCount: ItemCount 166 | ); 167 | _largeWebApiXmlService.Start(); 168 | 169 | _largeWebApiUtf8JsonService = new WebApiService( 170 | port: port++, 171 | format: SerializerType.Utf8Json, 172 | itemCount: ItemCount 173 | ); 174 | _largeWebApiUtf8JsonService.Start(); 175 | 176 | _largeAspNetCoreJsonNetService = new AspNetCoreService( 177 | port: port++, 178 | format:SerializerType.JsonNet, 179 | itemCount: ItemCount 180 | ); 181 | _largeAspNetCoreJsonNetService.Start(); 182 | 183 | _largeAspNetCoreMessagePackService = new AspNetCoreService( 184 | port: port++, 185 | format: SerializerType.MessagePack, 186 | itemCount: ItemCount 187 | ); 188 | _largeAspNetCoreMessagePackService.Start(); 189 | 190 | _largeAspNetCoreXmlService = new AspNetCoreService( 191 | port: port++, 192 | format: SerializerType.Xml, 193 | itemCount: ItemCount 194 | ); 195 | _largeAspNetCoreXmlService.Start(); 196 | 197 | _largeAspNetCoreUtf8JsonService = new AspNetCoreService( 198 | port: port++, 199 | format: SerializerType.Utf8Json, 200 | itemCount: ItemCount 201 | ); 202 | _largeAspNetCoreUtf8JsonService.Start(); 203 | } 204 | 205 | [GlobalCleanup] 206 | public void Cleanup() 207 | { 208 | _smallTextWcfService?.Stop(); 209 | _smallWebXmlWcfService?.Stop(); 210 | _smallWebJsonWcfService?.Stop(); 211 | _smallWebApiJsonNetService?.Stop(); 212 | _smallWebApiMessagePackService?.Stop(); 213 | _smallWebApiXmlService?.Stop(); 214 | _smallWebApiUtf8JsonService?.Stop(); 215 | _smallAspNetCoreJsonNetService?.Stop(); 216 | _smallAspNetCoreMessagePackService?.Stop(); 217 | _smallAspNetCoreXmlService?.Stop(); 218 | _smallAspNetCoreUtf8JsonService?.Stop(); 219 | _largeTextWcfService?.Stop(); 220 | _largeWebXmlWcfService?.Stop(); 221 | _largeWebJsonWcfService?.Stop(); 222 | _largeWebApiJsonNetService?.Stop(); 223 | _largeWebApiMessagePackService?.Stop(); 224 | _largeWebApiXmlService?.Stop(); 225 | _largeWebApiUtf8JsonService?.Stop(); 226 | _largeAspNetCoreJsonNetService?.Stop(); 227 | _largeAspNetCoreMessagePackService?.Stop(); 228 | _largeAspNetCoreXmlService?.Stop(); 229 | _largeAspNetCoreUtf8JsonService?.Stop(); 230 | } 231 | 232 | [Benchmark] 233 | public IReadOnlyCollection SmallWcfText() 234 | { 235 | return _smallTextWcfService.Invoke(); 236 | } 237 | 238 | [Benchmark] 239 | public IReadOnlyCollection SmallWcfWebXml() 240 | { 241 | return _smallWebXmlWcfService.Invoke(); 242 | } 243 | 244 | [Benchmark] 245 | public IReadOnlyCollection SmallWcfWebJson() 246 | { 247 | return _smallWebJsonWcfService.Invoke(); 248 | } 249 | 250 | [Benchmark] 251 | public Task> SmallWebApiJsonNet() 252 | { 253 | return _smallWebApiJsonNetService.Invoke(); 254 | } 255 | 256 | [Benchmark] 257 | public Task> SmallWebApiMessagePack() 258 | { 259 | return _smallWebApiMessagePackService.Invoke(); 260 | } 261 | 262 | [Benchmark] 263 | public Task> SmallWebApiXml() 264 | { 265 | return _smallWebApiXmlService.Invoke(); 266 | } 267 | 268 | [Benchmark] 269 | public Task> SmallWebApiUtf8Json() 270 | { 271 | return _smallWebApiUtf8JsonService.Invoke(); 272 | } 273 | 274 | [Benchmark] 275 | public Task> SmallAspNetCoreJsonNet() 276 | { 277 | return _smallAspNetCoreJsonNetService.Invoke(); 278 | } 279 | 280 | [Benchmark] 281 | public Task> SmallAspNetCoreMessagePack() 282 | { 283 | return _smallAspNetCoreMessagePackService.Invoke(); 284 | } 285 | 286 | [Benchmark] 287 | public Task> SmallAspNetCoreXml() 288 | { 289 | return _smallAspNetCoreXmlService.Invoke(); 290 | } 291 | 292 | [Benchmark] 293 | public Task> SmallAspNetCoreUtf8Json() 294 | { 295 | return _smallAspNetCoreUtf8JsonService.Invoke(); 296 | } 297 | 298 | [Benchmark] 299 | public IReadOnlyCollection LargeWcfText() 300 | { 301 | return _largeTextWcfService.Invoke(); 302 | } 303 | 304 | [Benchmark] 305 | public IReadOnlyCollection LargeWcfWebXml() 306 | { 307 | return _largeWebXmlWcfService.Invoke(); 308 | } 309 | 310 | [Benchmark] 311 | public IReadOnlyCollection LargeWcfWebJson() 312 | { 313 | return _largeWebJsonWcfService.Invoke(); 314 | } 315 | 316 | [Benchmark] 317 | public Task> LargeWebApiJsonNet() 318 | { 319 | return _largeWebApiJsonNetService.Invoke(); 320 | } 321 | 322 | [Benchmark] 323 | public Task> LargeWebApiMessagePack() 324 | { 325 | return _largeWebApiMessagePackService.Invoke(); 326 | } 327 | 328 | [Benchmark] 329 | public Task> LargeWebApiXml() 330 | { 331 | return _largeWebApiXmlService.Invoke(); 332 | } 333 | 334 | [Benchmark] 335 | public Task> LargeWebApiUtf8Json() 336 | { 337 | return _largeWebApiUtf8JsonService.Invoke(); 338 | } 339 | 340 | [Benchmark] 341 | public Task> LargeAspNetCoreJsonNet() 342 | { 343 | return _largeAspNetCoreJsonNetService.Invoke(); 344 | } 345 | 346 | [Benchmark] 347 | public Task> LargeAspNetCoreMessagePack() 348 | { 349 | return _largeAspNetCoreMessagePackService.Invoke(); 350 | } 351 | 352 | [Benchmark] 353 | public Task> LargeAspNetCoreXml() 354 | { 355 | return _largeAspNetCoreXmlService.Invoke(); 356 | } 357 | 358 | [Benchmark] 359 | public Task> LargeAspNetCoreUtf8Json() 360 | { 361 | return _largeAspNetCoreUtf8JsonService.Invoke(); 362 | } 363 | 364 | private WcfService _smallTextWcfService; 365 | private WcfService _smallWebXmlWcfService; 366 | private WcfService _smallWebJsonWcfService; 367 | private WebApiService _smallWebApiJsonNetService; 368 | private WebApiService _smallWebApiMessagePackService; 369 | private WebApiService _smallWebApiXmlService; 370 | private WebApiService _smallWebApiUtf8JsonService; 371 | private AspNetCoreService _smallAspNetCoreJsonNetService; 372 | private AspNetCoreService _smallAspNetCoreMessagePackService; 373 | private AspNetCoreService _smallAspNetCoreXmlService; 374 | private AspNetCoreService _smallAspNetCoreUtf8JsonService; 375 | private WcfService _largeTextWcfService; 376 | private WcfService _largeWebXmlWcfService; 377 | private WcfService _largeWebJsonWcfService; 378 | private WebApiService _largeWebApiJsonNetService; 379 | private WebApiService _largeWebApiMessagePackService; 380 | private WebApiService _largeWebApiXmlService; 381 | private WebApiService _largeWebApiUtf8JsonService; 382 | private AspNetCoreService _largeAspNetCoreJsonNetService; 383 | private AspNetCoreService _largeAspNetCoreMessagePackService; 384 | private AspNetCoreService _largeAspNetCoreXmlService; 385 | private AspNetCoreService _largeAspNetCoreUtf8JsonService; 386 | } 387 | } 388 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # WcfVsBenchmark 2 | A benchmark between WCF and other .NET web frameworks 3 | 4 | ## Comparisons 5 | This benchmark compares the response times of [WCF](https://docs.microsoft.com/en-us/dotnet/framework/wcf/whats-wcf), ASP.NET Web API and ASP.NET Core under various workloads. 6 | 7 | It spins up a number of web servers, and measures the duration of a round-trip where a number of items of varying complexity are sent to and received from the web server. 8 | 9 | The items are serialized and deserialized using different frameworks, namely [Newtonsoft.Json](https://www.newtonsoft.com/json), [MessagePack](https://github.com/neuecc/MessagePack-CSharp/), [XmlSerializer](https://docs.microsoft.com/en-us/dotnet/api/system.xml.serialization.xmlserializer?view=netframework-4.7.1), and [Utf8Json](https://github.com/neuecc/Utf8Json). 10 | 11 | ## Running 12 | To run this benchmark, you might have to start it running as Administrator, because the WCF bits use HTTP.sys. -------------------------------------------------------------------------------- /RestServiceBase.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Net.Http; 5 | using System.Net.Http.Formatting; 6 | using System.Threading.Tasks; 7 | 8 | namespace WcfVsWebApiVsAspNetCoreBenchmark 9 | { 10 | public class RestServiceBase 11 | where T: class, new() 12 | { 13 | protected void InitializeClients() 14 | { 15 | _client.BaseAddress = new Uri($"http://localhost:{_port}"); 16 | } 17 | 18 | public Task> Invoke() 19 | { 20 | return _invokeFunc(); 21 | } 22 | 23 | private async Task> InvokeJsonNet() 24 | { 25 | var response = await _client.PostAsJsonAsync($"api/operation/{typeof(T).Name}?itemCount={_itemCountToRequest}", _itemsToSend); 26 | response.EnsureSuccessStatusCode(); 27 | 28 | return await response.Content.ReadAsAsync(); 29 | } 30 | 31 | private async Task> InvokeMessagePack() 32 | { 33 | var response = await _client.PostAsync($"api/operation/{typeof(T).Name}?itemCount={_itemCountToRequest}", new ObjectContent(typeof(IReadOnlyCollection), _itemsToSend, _messagePackMediaTypeFormatter)); 34 | response.EnsureSuccessStatusCode(); 35 | 36 | return await response.Content.ReadAsAsync>(new[] { _messagePackMediaTypeFormatter }); 37 | } 38 | 39 | private async Task> InvokeXml() 40 | { 41 | var response = await _client.PostAsync($"api/operation/{typeof(T).Name}?itemCount={_itemCountToRequest}", new ObjectContent(typeof(IReadOnlyCollection), _itemsToSend, _xmlMediaTypeFormatter)); 42 | response.EnsureSuccessStatusCode(); 43 | 44 | return await response.Content.ReadAsAsync(new[] { _xmlMediaTypeFormatter }); 45 | } 46 | 47 | private async Task> InvokeUtf8Json() 48 | { 49 | var response = await _client.PostAsync($"api/operation/{typeof(T).Name}?itemCount={_itemCountToRequest}", new ObjectContent(typeof(IReadOnlyCollection), _itemsToSend, _utf8JsonMediaTypeFormatter)); 50 | response.EnsureSuccessStatusCode(); 51 | 52 | return await response.Content.ReadAsAsync(new[] { _utf8JsonMediaTypeFormatter }); 53 | } 54 | 55 | protected RestServiceBase(int port, SerializerType format, int itemCount) 56 | { 57 | _port = port; 58 | _format = format; 59 | 60 | _itemsToSend = Cache.Get().Take(itemCount).ToArray(); 61 | _itemCountToRequest = itemCount; 62 | 63 | _invokeFunc = GetInvokeFunction(); 64 | } 65 | 66 | private Func>> GetInvokeFunction() 67 | { 68 | switch(_format) 69 | { 70 | case SerializerType.Xml: 71 | return InvokeXml; 72 | case SerializerType.JsonNet: 73 | return InvokeJsonNet; 74 | case SerializerType.MessagePack: 75 | return InvokeMessagePack; 76 | case SerializerType.Utf8Json: 77 | return InvokeUtf8Json; 78 | default: 79 | throw new ArgumentOutOfRangeException(nameof(_format), _format, null); 80 | } 81 | } 82 | 83 | private readonly int _port; 84 | private readonly SerializerType _format; 85 | private readonly T[] _itemsToSend; 86 | private readonly int _itemCountToRequest; 87 | private readonly MessagePackMediaTypeFormatter _messagePackMediaTypeFormatter = new MessagePackMediaTypeFormatter(); 88 | private readonly Utf8JsonMediaTypeFormatter _utf8JsonMediaTypeFormatter = new Utf8JsonMediaTypeFormatter(); 89 | 90 | private readonly XmlMediaTypeFormatter _xmlMediaTypeFormatter = new XmlMediaTypeFormatter 91 | { 92 | UseXmlSerializer = true, 93 | }; 94 | 95 | private readonly HttpClient _client = new HttpClient(); 96 | private readonly Func>> _invokeFunc; 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /SerializerType.cs: -------------------------------------------------------------------------------- 1 | namespace WcfVsWebApiVsAspNetCoreBenchmark 2 | { 3 | public enum SerializerType 4 | { 5 | Xml, 6 | JsonNet, 7 | MessagePack, 8 | Utf8Json 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /SmallItem.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | using MessagePack; 4 | 5 | namespace WcfVsWebApiVsAspNetCoreBenchmark 6 | { 7 | [MessagePackObject] 8 | public class SmallItem 9 | { 10 | [Key(0)] 11 | public Guid Id { get; set; } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /Utf8JsonMediaTypeFormatter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Net; 4 | using System.Net.Http; 5 | using System.Net.Http.Formatting; 6 | using System.Net.Http.Headers; 7 | using System.Threading.Tasks; 8 | 9 | namespace WcfVsWebApiVsAspNetCoreBenchmark 10 | { 11 | public class Utf8JsonMediaTypeFormatter: MediaTypeFormatter 12 | { 13 | public override bool CanReadType(Type type) 14 | { 15 | return true; 16 | } 17 | 18 | public override bool CanWriteType(Type type) 19 | { 20 | return true; 21 | } 22 | 23 | public override async Task ReadFromStreamAsync(Type type, Stream readStream, HttpContent content, IFormatterLogger formatterLogger) 24 | { 25 | return await Utf8Json.JsonSerializer.DeserializeAsync(readStream); 26 | } 27 | 28 | public override Task WriteToStreamAsync(Type type, object value, Stream writeStream, HttpContent content, TransportContext transportContext) 29 | { 30 | return Utf8Json.JsonSerializer.SerializeAsync(writeStream, value); 31 | } 32 | 33 | public Utf8JsonMediaTypeFormatter() 34 | { 35 | SupportedMediaTypes.Add(new MediaTypeHeaderValue("application/json")); 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /WcfService.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using System.ServiceModel; 4 | using System.ServiceModel.Channels; 5 | using System.ServiceModel.Description; 6 | using System.ServiceModel.Web; 7 | 8 | namespace WcfVsWebApiVsAspNetCoreBenchmark 9 | { 10 | public class WcfService 11 | where T: class, new() 12 | { 13 | public void Start() 14 | { 15 | _host = new WebServiceHost(typeof(WcfServiceImpl), new Uri($"http://localhost:{_port}/")); 16 | AddServiceEndPoint(); 17 | 18 | _channelFactory = CreateChannelFactory(); 19 | _host.Open(); 20 | } 21 | 22 | private void AddServiceEndPoint() 23 | { 24 | switch(_bindingType) 25 | { 26 | case WcfBindingType.BasicText: 27 | AddServiceEndpoint(new BasicHttpBinding { MessageEncoding = WSMessageEncoding.Text, MaxReceivedMessageSize = 1024 * 1024 * 1024 }); 28 | break; 29 | case WcfBindingType.WebXml: 30 | { 31 | var endpoint = AddServiceEndpoint(new WebHttpBinding { MaxReceivedMessageSize = 1024 * 1024 * 1024 }); 32 | endpoint.Behaviors.Add(new WebHttpBehavior { DefaultOutgoingRequestFormat = WebMessageFormat.Xml, DefaultOutgoingResponseFormat = WebMessageFormat.Xml, DefaultBodyStyle = WebMessageBodyStyle.Wrapped }); 33 | break; 34 | } 35 | case WcfBindingType.WebJson: 36 | { 37 | var endpoint = AddServiceEndpoint(new WebHttpBinding { MaxReceivedMessageSize = 1024 * 1024 * 1024 }); 38 | endpoint.Behaviors.Add(new WebHttpBehavior { DefaultOutgoingRequestFormat = WebMessageFormat.Json, DefaultOutgoingResponseFormat = WebMessageFormat.Json, DefaultBodyStyle = WebMessageBodyStyle.Wrapped }); 39 | break; 40 | } 41 | default: 42 | throw new ArgumentOutOfRangeException(); 43 | } 44 | 45 | ServiceEndpoint AddServiceEndpoint(Binding binding) 46 | { 47 | return _host.AddServiceEndpoint(typeof(IWcfService), binding, $"http://localhost:{_port}"); 48 | } 49 | } 50 | 51 | private ChannelFactory> CreateChannelFactory() 52 | { 53 | switch(_bindingType) 54 | { 55 | case WcfBindingType.BasicText: 56 | return CreateChannelFactory(new BasicHttpBinding { MessageEncoding = WSMessageEncoding.Text, MaxReceivedMessageSize = 1024 * 1024 * 1024 }); 57 | case WcfBindingType.WebXml: 58 | { 59 | var factory = CreateChannelFactory(new WebHttpBinding { MaxReceivedMessageSize = 1024 * 1024 * 1024 }); 60 | factory.Endpoint.Behaviors.Add(new WebHttpBehavior { DefaultOutgoingRequestFormat = WebMessageFormat.Xml, DefaultOutgoingResponseFormat = WebMessageFormat.Xml, DefaultBodyStyle = WebMessageBodyStyle.Wrapped }); 61 | 62 | return factory; 63 | } 64 | case WcfBindingType.WebJson: 65 | { 66 | var factory = CreateChannelFactory(new WebHttpBinding { MaxReceivedMessageSize = 1024 * 1024 * 1024 }); 67 | factory.Endpoint.Behaviors.Add(new WebHttpBehavior { DefaultOutgoingRequestFormat = WebMessageFormat.Json, DefaultOutgoingResponseFormat = WebMessageFormat.Json, DefaultBodyStyle = WebMessageBodyStyle.Wrapped }); 68 | 69 | return factory; 70 | } 71 | default: 72 | throw new ArgumentOutOfRangeException(); 73 | } 74 | 75 | ChannelFactory> CreateChannelFactory(Binding binding) 76 | { 77 | return new ChannelFactory>(binding, $"http://localhost:{_port}"); 78 | } 79 | } 80 | 81 | public T[] Invoke() 82 | { 83 | var channel = _channelFactory.CreateChannel(); 84 | 85 | return channel.Operation(_itemsToSend, _itemCountToRequest); 86 | } 87 | 88 | public void Stop() 89 | { 90 | _channelFactory?.Close(); 91 | _host.Close(); 92 | } 93 | 94 | public WcfService(int port, WcfBindingType bindingType, int itemCount) 95 | { 96 | _port = port; 97 | _bindingType = bindingType; 98 | 99 | _itemsToSend = Cache.Get().Take(itemCount).ToArray(); 100 | _itemCountToRequest = itemCount; 101 | } 102 | 103 | private readonly int _port; 104 | private readonly WcfBindingType _bindingType; 105 | private readonly T[] _itemsToSend; 106 | private readonly int _itemCountToRequest; 107 | private WebServiceHost _host; 108 | private ChannelFactory> _channelFactory; 109 | } 110 | 111 | public enum WcfBindingType 112 | { 113 | BasicText, 114 | WebXml, 115 | WebJson, 116 | } 117 | 118 | [ServiceContract] 119 | public interface IWcfService 120 | { 121 | [OperationContract] 122 | T[] Operation(T[] items, int itemCount); 123 | } 124 | 125 | public class WcfServiceImpl: IWcfService, IWcfService 126 | { 127 | public SmallItem[] Operation(SmallItem[] items, int itemCount) 128 | { 129 | return Cache.SmallItems.Take(itemCount).ToArray(); 130 | } 131 | 132 | public LargeItem[] Operation(LargeItem[] items, int itemCount) 133 | { 134 | return Cache.LargeItems.Take(itemCount).ToArray(); 135 | } 136 | } 137 | } 138 | -------------------------------------------------------------------------------- /WcfVsWebApiVsAspNetCoreBenchmark.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | Exe 4 | net471 5 | AnyCPU 6 | win10-x64 7 | 7.1 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /WcfVsWebApiVsAspNetCoreBenchmark.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WcfVsWebApiVsAspNetCoreBenchmark", "WcfVsWebApiVsAspNetCoreBenchmark.csproj", "{A106EF3F-A836-4935-AC36-34FBBD2EB9CE}" 4 | EndProject 5 | Global 6 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 7 | Debug|Any CPU = Debug|Any CPU 8 | Release|Any CPU = Release|Any CPU 9 | EndGlobalSection 10 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 11 | {A106EF3F-A836-4935-AC36-34FBBD2EB9CE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 12 | {A106EF3F-A836-4935-AC36-34FBBD2EB9CE}.Debug|Any CPU.Build.0 = Debug|Any CPU 13 | {A106EF3F-A836-4935-AC36-34FBBD2EB9CE}.Release|Any CPU.ActiveCfg = Release|Any CPU 14 | {A106EF3F-A836-4935-AC36-34FBBD2EB9CE}.Release|Any CPU.Build.0 = Release|Any CPU 15 | EndGlobalSection 16 | EndGlobal 17 | -------------------------------------------------------------------------------- /WebApiService.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using System.Net.Http.Formatting; 4 | using System.Web.Http; 5 | 6 | using Microsoft.Owin.Hosting; 7 | 8 | using Owin; 9 | 10 | namespace WcfVsWebApiVsAspNetCoreBenchmark 11 | { 12 | public class WebApiService: RestServiceBase 13 | where T: class, new() 14 | { 15 | public void Start() 16 | { 17 | var startup = new WebApiStartup(_format); 18 | _app = WebApp.Start($"http://localhost:{_port}", startup.Configuration); 19 | InitializeClients(); 20 | } 21 | 22 | public void Stop() 23 | { 24 | _app.Dispose(); 25 | } 26 | 27 | public WebApiService(int port, SerializerType format, int itemCount) 28 | : base(port, format, itemCount) 29 | { 30 | _port = port; 31 | _format = format; 32 | } 33 | 34 | private readonly int _port; 35 | private readonly SerializerType _format; 36 | private IDisposable _app; 37 | } 38 | 39 | public class WebApiController: ApiController 40 | { 41 | [HttpPost, Route("api/operation/SmallItem")] 42 | public SmallItem[] ItemOperation([FromBody] SmallItem[] items, int itemCount) 43 | { 44 | return Cache.SmallItems.Take(itemCount).ToArray(); 45 | } 46 | 47 | [HttpPost, Route("api/operation/LargeItem")] 48 | public LargeItem[] LargeItemOperation([FromBody] LargeItem[] items, int itemCount) 49 | { 50 | return Cache.LargeItems.Take(itemCount).ToArray(); 51 | } 52 | } 53 | 54 | public class WebApiStartup 55 | { 56 | public void Configuration(IAppBuilder app) 57 | { 58 | var config = new HttpConfiguration(); 59 | 60 | config.MapHttpAttributeRoutes(); 61 | config.Formatters.Clear(); 62 | 63 | switch(_format) 64 | { 65 | case SerializerType.Xml: 66 | config.Formatters.Add(new XmlMediaTypeFormatter 67 | { 68 | UseXmlSerializer = true, 69 | }); 70 | break; 71 | case SerializerType.JsonNet: 72 | config.Formatters.Add(new JsonMediaTypeFormatter()); 73 | break; 74 | case SerializerType.MessagePack: 75 | config.Formatters.Add(new MessagePackMediaTypeFormatter()); 76 | break; 77 | case SerializerType.Utf8Json: 78 | config.Formatters.Add(new Utf8JsonMediaTypeFormatter()); 79 | break; 80 | default: 81 | throw new ArgumentOutOfRangeException(); 82 | } 83 | 84 | app.UseWebApi(config); 85 | } 86 | 87 | public WebApiStartup(SerializerType format) 88 | { 89 | _format = format; 90 | } 91 | 92 | private readonly SerializerType _format; 93 | } 94 | } 95 | --------------------------------------------------------------------------------