Transitioning from ASP.NET Web API 2 to ASP.NET MVC 6

Posted by: Filip W , on 7/5/2015, in Category ASP.NET MVC
Views: 86390
Abstract: Transitioning from HTTP API development using ASP.NET Web API to the new world of ASP.NET 5 and MVC 6 can be challenging. This article will highlight the large and subtle differences and will help you out with the transition

This article will look at the parallels between the Web API framework pipeline and the upcoming MVC 6 framework - attempting to ease your transition into the new ASP.NET 5 world.


While some of the concepts (controllers, filters and so on) may look similar at first glance, because of Microsoft's approach to rewrite everything from scratch, transitioning your applications from current Web API application model to MVC 6 is actually not that simple.

There are obviously plenty of online - and offline - resources from both Microsoft and the community, discussing various new features of the ASP.NET MVC 6 framework. However, if you are worried about backward compatibility issues, porting your custom pipeline modifications or understanding that gap between Web API 2 and MVC 6 - then this article is for you.

This article is published from the DNC Magazine for .NET Developers and Architects. Download this magazine from here [PDF] or Subscribe to this magazine for FREE and download all previous and current editions


The first place to look at when you need to migrate an existing Web API 2 project to MVC 6 is the package called Microsoft.AspNet.Mvc.WebApiCompatShim.

ASP.NET MVC 6 is a very extensible framework - and WebApiCompatShim provides just that - a nice bridge between the old Web API 2 world and the modern reality of ASP.NET 5.

It is a layer of framework settings and conventions that transform the default MVC 6 behavior into behavior you might be more used to, from Web API 2.

What's interesting is that even the namespace of the types that WebApiCompatShim introduces, that are missing in MVC 6 but existed in Web API; is the same as it used to be in the Web API framework. Additionally, the shim also references System.Net.Http for you in order to reintroduce the familiar classes of HttpRequestMessage and HttpResponseMessage.

All of this to minimize the pain related to porting your application. To enable the shim, you call the following extension method at your application startup, inside the ConfigureServices methods:



MVC 6 does not have dedicated controllers for Web API - this is because it unifies both MVC (web pages) and Web API (web apis).

WebApiCompatShim contains an ApiController class which mimics the structure of the Web API ApiController. It contains similar properties that your old Web API code might be using, such as User, Request; as well as same action helpers - methods returning IHttpActionResult.

While for new projects it is advisable to avoid ApiController and simply build around the common MVC 6 Controller base class (or even leverage the POCO-controller capabilities), if you are migrating from Web API 2, and a lot of your code was leaning on the members exposed by ApiController in Web API, it certainly can save you lots of hassle.

An interesting note is that ApiController in MVC 6 is actually a POCO-controller itself - it does not inherit from Controller base class.


WebApiCompatShim introduces a customized binder, HttpRequestMessageModelBinder, which allows you to bind an instance of System.Net.Http.HttpRequestMessage directly as a parameter of your action.

This behavior was possible in Web API and you might have some existing code relying on this capability.

public async Task<Ienumerable<Car>> Get(HttpRequestMessage request)
    //access "request" parameter

You don't need to do anything special to enable this behavior - as long as WebApiCompatShim is configured, you can simply use HttpRequestMessage in any action. Additionally, an instance of HttpRequestMessage is also exposed as a Request property of the ApiController.

This is really useful, as there are a lot of tools i.e. logging ones, that work directly with HttpRequestMessage instance, rather than the new Microsoft.AspNet.Http.HttpContext around which ASP.NET 5 is built.

The aforementioned HttpRequestMessageModelBinder will take care of creating an instance of HttpRequestMessage from the HttpContext on every request.

On top of all that, the WebApiCompatShim will also introduce several extension methods that people commonly used in ASP.NET Web API. They are listed here:

public static class HttpRequestMessageExtensions
    public static HttpResponseMessage CreateErrorResponse(this HttpRequestMessage request,
        InvalidByteRangeException invalidByteRangeException);

    public static HttpResponseMessage CreateErrorResponse(this HttpRequestMessage request,
        HttpStatusCode statusCode, string message);

    public static HttpResponseMessage CreateErrorResponse(this HttpRequestMessage request,
        HttpStatusCode statusCode, string message, Exception exception);

    public static HttpResponseMessage CreateErrorResponse(this HttpRequestMessage request,
        HttpStatusCode statusCode, Exception exception);

    public static HttpResponseMessage CreateErrorResponse(this HttpRequestMessage request,
        HttpStatusCode statusCode, ModelStateDictionary modelState);

    public static HttpResponseMessage CreateErrorResponse(this HttpRequestMessage request,
        HttpStatusCode statusCode, HttpError error);

    public static HttpResponseMessage CreateResponse<T>(this HttpRequestMessage request, T value);

    public static HttpResponseMessage CreateResponse<T>(this HttpRequestMessage request,
        HttpStatusCode statusCode, T value);

    public static HttpResponseMessage CreateResponse<T>(this HttpRequestMessage request,
        HttpStatusCode statusCode, T val

As you can see, the extension methods include a bunch of types that were Web API specific (MediaTypeFormatter, HttpError) or System.Net.Http specific (MediaTypeHeaderValue).

The shim introduces several other pain-relieving mechanisms, all of which I have discussed extensively in an article at

Old concepts mapped to new world

Message Handlers vs Middleware

In ASP.NET Web API, the way of dealing with cross-cutting concerns, such as logging or caching was through message handlers. The concept of message handlers was very simple, but very powerful. The handlers were chained one after the other, and each got a chance to process the incoming HTTP request and outgoing HTTP response. Together they formed a so called "Russian doll model", because the handler that interacted with the request first (the outer-most handler), got to interact with the response last.

The example here shows a typical Web API message handler (created off the base DelegatingHandler class).

public class MyMessageHandler : DelegatingHandler
    protected async override Task<HttpResponseMessage> SendAsync(
        HttpRequestMessage request, CancellationToken cancellationToken)
        //process request
        // Call the next (inner) handler.
        var response = await base.SendAsync(request, cancellationToken);
        //process response
        return response;

The idea of handlers is sadly no longer present in ASP.NET 5, and the closest concept that can allow you to achieve same behavior and support the same functionality are OWIN middleware components. While going into the details of OWIN middleware is beyond the scope of this article, the fact that ASP.NET 5 is a de facto OWIN implementation allows you to plug in middleware in front of the MVC pipeline.

This is also a key difference between middleware and message handlers - middleware components are technically speaking, located outside of MVC 6 pipeline, while message handlers were located inside the Web API pipeline (albeit, at its very forefront, before controller selection).

There are various ways of writing middleware - a raw middleware has a rather unfriendly structure shown below:

Func<Func<IDictionary<string, object>, Task>, Func<IDictionary<string, object>, Task>>

You can, however, write ASP.NET 5 middleware in an object-oriented way, using the strongly typed HttpContext from the new HTTP abstractions. It will be the same HttpContext with which you get to interact i.e. inside of MVC 6 controllers. This is shown below.

public class MyMiddleware
    private readonly RequestDelegate _next;

    public MyMiddleware(RequestDelegate next)
        _next = next;

    public async Task Invoke(HttpContext context)
        //process context.Request
        await _next(context);
        //process context.Response

Web API Filters vs MVC 6 Filters

Filters were the typical component used in Web API to wrap the functionality of your actions with some extra logic. Web API 2 had 4 types of filters:

  • action filters - for general aspect oriented functionalities, allowing you to invoke code before and after action execution
  • authentication filters - introduced in Web API 2, allowed you to leverage host-level authentication provided via OWIN security middleware
  • authorization filters - used for authorization (and in Web API 1, quite often, also for (!) authentication)
  • exception filters - exception handling on an action-level

In ASP.NET 5, all the authentication logic has been moved to the Microsoft.AspNet.Authentication and related Microsoft.AspNet.Authentication.* packages. This is where all the security middleware - direct successors to Katana Project security components are located. ASP.NET MVC 6 integrates with them deeply, and as a result, there is no need for authentication filters anymore.

ASP.NET MVC 6 still uses action filters (IActionFilter), exception filters (IExceptionFilter) and auhtorization filters (IAuthorizationFilter), and they are virtually identical to those in Web API 2, so the transition in those areas should be quite straight forward.

Interestingly, aside from the "classic" filters mentioned above, ASP.NET MVC 6 introduces two extra types of filters, which are really specialized versions of IActionFilters:

  • TypeFilters
  • ServiceFilter

TypeFilters allow you to have a custom filter class instantiated on demand for each request (potentially with extra parameters). Consider the following example:

public ActionResult Process();

This allows you to write a MyFilter class, implementing IFilter itself, and have it instantiated for you on every request. Why would you want this? To have constructor dependency injection into your filter! Normally this was not allowed in Web API without heavily modifying the filter pipeline. In this case, MyFilter could look as follows:

public class MyFilter : IActionFilter
    private IMyFancyService _fancy;

    public MyFilter(IMyFancyService fancy)
        _fancy = fancy;

    public void OnActionExecuted(ActionExecutedContext context)
      //do stuff

    public void OnActionExecuting(ActionExecutingContext context)
      //do stuff

In other words, IMyFancyService above is actually being resolved from the built-in ASP.NET 5 IoC container. You can also pass some extra arguments to the constructor of MyFilter explicitly - TypeFilter exposes Arguments object array for that.

ServiceFilters are similar, only a bit simpler. Consider the following signature:

public ActionResult Process();

In this case, OtherFilter will be immediately resolved from the ASP.NET 5 IoC container (where you'd normally register services) - hence the name "service". Obviously, that type would also need to implement IFilter. Because the filter in this case would be attempted to be obtained from the container, it must have been registered in it in the first place, otherwise this code would produce an error. On the contrary, in our previous example, our MyFilter class didn't need to be registered in the container - however it could consume dependencies that were coming from the container.

One extra feature worth mentioning is that MVC 6 filters are sortable out-of-the-box, so each filter will have an Order property. Web API 2 lacked this feature and that was actually quite constraining.

DependencyResolver vs built in dependency injection

In ASP.NET Web API, the most common way to enable dependency injection was to use the IDependencyResolver abstraction. Out-of-the-box, DI was not possible - you had to explicitly enable it by plugging in one of the community-provided implementations; pretty much every major IoC container for .NET had its own IDependencyResolver version.

Another option in Web API was to create a custom IHttpControllerActivator, which was responsible for instantiating controllers. There you could either resolve them by hand, or from a global DI container of your choice.

As far as dependency injection into other components in the Web API pipeline - message handlers, filters, formatters - constructor dependency injection was generally not supported. The only reasonable workaround was to use a service-locator like approach - obtain an instance of a registered IDependencyResolver from the current HttpRequestMessage and use it to resolve the necessary dependencies.

In ASP.NET MVC 6 things are much cleaner - as the underlying ASP.NET 5 runtime supports dependency injection out-of-the-box, without the need of plugging in an external IoC container. The built-in ASP.NET 5 IoC container is not very sophisticated, but it will fill the majority of needs most of the time.

You configure the ASP.NET 5 DI in the Startup class, inside the ConfigureServices method.

public void ConfigureServices(IServiceCollection services)
    services.AddSingleton<IService>(new MyService());
    services.AddTransient<IAnotherService, AnotherService>();

With the set up shown above, you can inject both IService and IAnotherService into any controller through a constructor (inlcuding POCO controllers!). You can also inject the dependencies into a controller's property, using the [FromServices] attribute.

public class MyController : Controller 
    public IService Service { get; set; }
    //rest of controller omitted for brevity

These dependencies will also be hydrated if you inject them into the Invoke method of middleware components:

public async Task Invoke(HttpContext context, IService service)
    //do stuff with service
    await _next(context);

Finally, if you create ServiceFilterAttributes or TypeFilterAttributes, you can also inject your dependencies into them through constructor.

ASP.NET MVC 6 also supports external IoC adapters - and those can be used to govern the dependency injection resolution in your application instead of the built in one. In that case, you'd use a different version of ConfigureServices method. The example here shows a simple set up of Autofac container:

public IServiceProvider ConfigureServices(IServiceCollection services)
    //create Autofac container build
    var builder = new ContainerBuilder();

    //populate the container with services here..

    //build container
    var container = builder.Build();
    //return service provider
    return container.Resolve<IServiceProvider>();

Different model binding

The out-of-the-box model binding behavior in MVC 6 is much different than that of Web API and is more closely aligned with the MVC 5 model.

MVC 5 used to model bind over everything in the request - that is both query string and body of the request. In other words, a complex type that is being accepted as a parameter of your action could have one property bound from the body and another from the query string. On the other hand, Web API provided strict differentiation between the body - which was unbuffered and read using formatters, and query parameters which were read using model binders.


MVC 6 also uses binding and formatters, however the behavior is much different from what you might be used to when building APIs with Web API. Consider the following model and a controller:

public class Item    
    public int Quantity { get; set; }        
    public string Name { get; set; }    

public class ItemController
    public void Post(Item item) {}

If your action accepts this model as an input parameter, the following requests are valid in MVC 6:

  • POST /item?quantity=1&name=foo, empty body
  • POST /item?quantity=1, body: name=foo, content-type: application/x-www-form-urlencoded
  • POST /item, body: name=foo&quantity=1, content-type: application/x-www-form-urlencoded

In other words, you are able to mix-and-match between body parameters and query parameters, however - and this is very important - this approach will only work for application/x-www-form-urlencoded content type. If you try to send your body as JSON or XML, the default MVC 6 input binding behavior will not pick it up, and only bind from query string! MVC 6 does not even support working with JSON out of the box - the following request will not be bound correctly:

POST /item
Content-Type: application/json
{"name": "foo", "quantity": 1}

This is obviously much different from the traditional Web API behavior and can come back to haunt you if you are not aware of these model binding changes.

In order to force your API to use formatters to deserialize JSON/XML input from the body, you have to explicitly decorate your action parameter with [FromBody] attribute.

public void Post([FromBody]Item item) {}

You can also do it globally, for your entire application, which changes the default behavior of MVC 6 to more Web API-like. The code below utilizes an extensibility point called IApplicationModelConvention that allows you to iterate through all controllers, actions and parameters of all MVC 6 endpoints at the application startup and programmatically alter them. Because of that, you can inject FromBodyAttribute without having to manually adding it everywhere. In there you could also establish your own convention or logic about when this given attribute should be applied.

public class FromBodyApplicationModelConvention : IApplicationModelConvention
    public void Apply(ApplicationModel application)
        foreach (var controller in application.Controllers)
            foreach (var action in controller.Actions)
                foreach (var parameter in action.Parameters)
                    if (parameter.BinderMetadata is IBinderMetadata || ValueProviderResult.CanConvertFromString(parameter.ParameterInfo.ParameterType))
                        // behavior configured or simple type so do nothing
                        // Complex types are by-default from the body.
                        parameter.BinderMetadata = new FromBodyAttribute();

Then you have to enable this convention in the Startup class.

services.Configure<MvcOptions>(opt =>
    opt.ApplicationModelConventions.Add(new FromBodyApplicationModelConvention());

Similarly as in Web API, once you have decided to use [FromBody] in your action, you can only read one thing from the body, that is the following signature will be invalid:

public void Post([FromBody]Item item1, [FromBody]Item item2) {}

On the other hand, if you remove [FromBody] from one of the parameters, you can still utilize query string and body binding together:

public void Post([FromBody]Item item1, Item item2) {} 
//item1 from body, item2 from querystring
POST /item?name=nameforitem2&quantity-4
Content-Type: application/json
{"name": "nameforitem1", "quantity": 1}

Formatters in MVC 6

Since MVC 6 is a unified MVC/Web API framework, it is very natural that the concept of formatters (previously only present in Web API) was brought forward to the new world.

However, in MVC 6, formatters have been split into input- and output-formatters, instead of the traditional single, bi-directional formatter types that Web API used. On top of that, we have already mentioned in the previous section that in order to force MVC 6 to use input formatters in the first place, you must annotate your action parameter with [FromBody].

Aside from that, the formatters are pretty much the same - they have to declare two things:

  • a flag whether they can read / write a given type - since a specific formatter may not be applicable to a specific type, for example not every user type can be represented as RSS
  • they read from request stream / write to response stream process

Output formatters should also define which content types they support. The formatter interfaces are listed here:

public interface IOutputFormatter
    IReadOnlyList<MediaTypeHeaderValue> GetSupportedContentTypes(
        Type declaredType, 
        Type runtimeType, 
        MediaTypeHeaderValue contentType);

    bool CanWriteResult(OutputFormatterContext context, MediaTypeHeaderValue contentType);

    Task WriteAsync(OutputFormatterContext context);

public interface IInputFormatter
    bool CanRead(InputFormatterContext context);

    Task<object> ReadAsync(InputFormatterContext context);

What is important to remember,as opposed to Web API, is that XML formatters are disabled by default in MVC 6. If your API needs to support XML, you will need to bring in the Microsoft.AspNet.Mvc.Xml package and add XmlFormatters by hand:

services.Configure<MvcOptions>(options =>
    //this adds both inout and output formatters

Instead, out of the box MVC 6 contains only one input formatter and three output formatters:

  • JsonInputFormatter / JsonOutputFormatter
  • PlainTextFormatter - which didn't exist in Web API and kicks in when you return a string from your actions, producing text/plain response. This is an important distinction, as Web API would produce an application/json response
  • HttpNoContentOutputFormatter - also did not exist in Web API. It is responsible for creating a response with status code 204 when your action returns a void or Task. The behavior in this case is identical to Web API, however it was handled differently there (through a service called ResultConverter)

IHttpActionResult vs IActionResult

One of the popular additions to ASP.NET Web API 2 was IHttpActionResult, which was used as a return type from an action, and was really an interface representing various factories for HttpResponseMessage. In fact, Web API shipped with a large number of implementations of IHttpActionResult in the box that you could use straight away in your API - such as BadRequestResult or CreatedNegotiatedContentResult<T> to name a few.

On the other hand, classic MVC framework has long had ActionResult as a base abstract type representing various responses. MVC 6 has been aligned closely with that approach, and IActionResult is the new abstraction that should be used in your actions. Since Web API and MVC frameworks have been unified in MVC 6, various IActionResult implementations can handle both traditional API scenarios (i.e. content negotiated object responses, No Content responses) and traditional MVC scenarios (i.e. view results).

public interface IActionResult
    Task ExecuteResultAsync(ActionContext context);

Obviously, there are plenty of implementations of that interface that come bundled in the framework itself, many of which can be mapped one to one to IHttpActionResult implementations from Web API - for example BadRequestObjectResult is the rough equivalent of the old BadRequestResult. When building HTTP APIs with MVC 6, you will find yourself working the most with ObjectResult which is the type of IActionResult that has content negotiation built in - more on that later.

Interestingly, when working with Web API, one of the annoyances was the lack of IHttpActionResults that dealt with serving files, and more generally speaking, binary content. Such components would have to be built by hand or referenced through open source packages that grew around Web API in its vibrant community. This has been addressed in MVC 6, which introduces several specialized action results in that area - FileContentResult, FilePathResult, FileResult and FileStreamResult

Content negotiation

In ASP.NET Web API, content negotiation was handled by IContentNegotiator interface. Normally, you would not use it directly, as the framework would handle content negotiation for you - when you returned a POCO object from an action (instead of HttpResponseMessage or IHttpActionResult), or when you called one of the extension methods on the HttpRequestMessage (i.e. CreateResponse). However, in some scenarios (for example determining the response media types for caching purposes) you'd need direct access to the ConNeg engine.

To facilitate these types of (now legacy) use cases, WebApiCompatibilityShim actually reintroduces all of the types involved in the content negotiation - so not only IContentNegotiator, but also DefaultContentNegotiator (the default implementation) or ContentNegotiationResult. So all your old content negotiation code can be pretty much ported verbatim to ASP.NET MVC 6.

On the other hand, ASP.NET MVC 6 does not have explicit service dedicated to running content negotiation. Instead, the logic responsible for selecting the relevant formatter is baked into the ObjectResult class. Inside its ExecuteResultAsync, responsible for writing to the response stream, the framework will walk through the available formatters and select a relevant one.

The logic for choosing a formatter is similar to that in ASP.NET Web API, and based on the following order of precedence:

  • Accept header
  • Content-Type header
  • selection based on type match

ASP.NET Web API also had a very useful concept of MediaTypeMappings. Mappings allowed you to override content negotiation for specific request structure - an extension (i.e. .json), querystring (i.e. ?format-json) or a predefined header (i.e. Format: json).

While MediaTypeMapping as a base class (that's how it was used in Web API) has no direct counterpart in ASP.NET MVC 6, the notion of media type mapping is indeed present there. MvcOptions exposes an object called FormatterMappings, which you can use (through SetMediaTypeMappingForFormat method) to map specific media types to a predefined string such as json or .json.

"pdf", MediaTypeHeaderValue.Parse("text/pdf"));

The whole mechanism is a little less extensible than it used to be in Web API - in order to use this, you are forced to create a route with a format placeholder or use a format querystring. Example routes are shown below:

app.UseMvc(routes =>
                    new { controller = "Home", action = "Index" });

                    new { controller = "Home", action = "Index" });

What's interesting is that ASP.NET MVC 6 maps json format to application/json out of the box. This means that by simply appending a ?format=json querystring to a request, you will always get a JSON response. You can disable this behavior by calling ClearMediaTypeMappingForFormat on FormatterMappings object.


It is also worth mentioning, that if you wish MVC 6 to issue 406 (Not Acceptable) response codes for situations when content negotiation is unsuccessful - the framework defaults to JSON responses if it can't determine the media type - you do it by simply inserting a new formatter, HttpNotAcceptableOutputFormatter to the formatters collection. This is slightly different approach from Web API, where this type of functionality was controlled by tweaking the DefaultContentNegotiator and passing true into its constructor (representing excludeMatchOnTypeOnly parameter).

services.Configure<MvcOptions>(options =>
    options.OutputFormatters.Insert(0, new HttpNotAcceptableOutputFormatter());

HttpConfiguration vs MvcOptions

Finally, we should also briefly touch on the changes in the configuration of the framework. In Web API, everything was controlled through the HttpConfiguration type, which was the gateway to all important Web API components:

  • all services
  • filters
  • formatters
  • properties dictionary
  • routes (through extension methods)

In MVC 6 the configuration happens through MvcOptions object, which over the course of this article, we have grown familiar with already. The outline is shown below:

public class MvcOptions
    public AntiForgeryOptions AntiForgeryOptions {get; set;}

    public FormatterMappings FormatterMappings { get; }

    public ICollection<IFilter> Filters { get; private set; }

    public IList<OutputFormatterDescriptor> OutputFormatters { get; }

    public IList<InputFormatterDescriptor> InputFormatters { get; }

    public IList<ExcludeValidationDescriptor> ValidationExcludeFilters { get; }

    public int MaxModelValidationErrors {get; set;}

    public IList<ModelBinderDescriptor> ModelBinders { get; }

    public IList<ModelValidatorProviderDescriptor> ModelValidatorProviders { get; }

    public IList<ViewEngineDescriptor> ViewEngines { get; }

    public IList<ValueProviderFactoryDescriptor> ValueProviderFactories { get; }

    public IList<IApplicationModelConvention> Conventions { get; }

    public bool RespectBrowserAcceptHeader { get; set; }

    public IDictionary<string, CacheProfile> CacheProfiles { get; }

In many ways, MvcOptions plays the same role as HttpConfiguration, providing a central configuration point for many runtime settings such as formatters, filters or model binders. Additionally, caching is also controlled through MvcOptions (something that was completely missing in Web API!).

As far as the actual services go (for example, DefaultFilterProvider, responsible for orchestrating the filter pipeline or DefaultControllerActivator, responsible for instantiating the controller types), they are registered against the ASP.NET 5 IoC container directly. When you call AddMvc() method on the IServiceCollection at the application startup (necessary to be able to use MVC 6 in your application at all), the framework will add all of the necessary runtime services to the container. You can then tweak or replace those by interacting with the container itself.


There are quite a few things to watch out for when trying to transition from HTTP API development using ASP.NET Web API to the new world of ASP.NET 5 and MVC 6. Aside from the obvious large scale changes, there are certainly some hidden landmines and subtle differences, and those are usually the most frustrating to deal with. However, if you pay attention to the points highlighted in this article, it shouldn’t be too frustrating of a task.

Let me close out by sharing an interesting thought from Darrel Miller, who, himself an HTTP guru, has been one of the most active members of the ASP.NET Web API community. He said recently on Twitter:

“How many WebAPI people would be more likely to use MVC6's WebAPICompatShim if it was named WebAPIConventions?”

And I think this hits the nail on the head. The Web API Compatibility Shim is a great way of enforcing specific coding standards and conventions, which have already proved to be useful and battle tested in ASP.NET Web API. Do not be wary about using them – even if you get shivers about a notion of using a “shim” in production. At the end of the day, the existence of the shim itself is just a testament to the extensibility of MVC 6.

This article has been editorially reviewed by Suprotim Agarwal.

Absolutely Awesome Book on C# and .NET

C# and .NET have been around for a very long time, but their constant growth means there’s always more to learn.

We at DotNetCurry are very excited to announce The Absolutely Awesome Book on C# and .NET. This is a 500 pages concise technical eBook available in PDF, ePub (iPad), and Mobi (Kindle).

Organized around concepts, this Book aims to provide a concise, yet solid foundation in C# and .NET, covering C# 6.0, C# 7.0 and .NET Core, with chapters on the latest .NET Core 3.0, .NET Standard and C# 8.0 (final release) too. Use these concepts to deepen your existing knowledge of C# and .NET, to have a solid grasp of the latest in C# and .NET OR to crack your next .NET Interview.

Click here to Explore the Table of Contents or Download Sample Chapters!

What Others Are Reading!
Was this article worth reading? Share it with fellow developers too. Thanks!
Share on LinkedIn
Share on Google+

Filip is a founder and maintainer of several popular open source projects, .NET blogger, author and a Microsoft MVP. He specializes in the Roslyn compiler, cross platform .NET development, ASP.NET Core and is experienced in delivering robust web solutions across the globe.Filip is based in Zürich where he works in a medical industry for Sonova AG. You can follow him on Twitter < a href = '' target='_blank'> @filip_woj .

Page copy protected against web site content infringement 	by Copyscape

Feedback - Leave us some adulation, criticism and everything in between!