In previous version of the WebApi you could do the following:
RouteTable.Routes.MapServiceRoute<UserService>("1.0/User/", defaultWebApiConfiguration);
RouteTable.Routes.MapServiceRoute<SomeOtherService>("1.0/SomeOtherService/", largeFilesConfig);
This would allow you to have different message handlers on different services. This is apparently not possible in the new framework: ASP.NET MVC 4 WebApi Support For Multiple HttpConfigurations
Alternatively I had projects where I edited the RequestHandlers in the WebApiConfiguration to add handlers if certain attributes existed like this:
public static void AppendAuthorizationRequestHandlers(
this WebApiConfiguration config)
{
var requestHandlers = config.RequestHandlers;
config.RequestHandlers = (c, e, od) =>
{
if (requestHandlers != null)
{
requestHandlers(c, e, od); // Original request handler
}
var authorizeAttribute = od.Attributes.OfType<RequireAuthorizationAttribute>()
.FirstOrDefault();
if (authorizeAttribute != null)
{
c.Add(new AuthOperationHandler(authorizeAttribute));
}
};
}
That code is based on: http://haacked.com/archive/2011/10/19/implementing-an-authorization-attribute-for-wcf-web-api.aspx. This is no longer possible as MessageHandlers on the HttpConfiguration is not settable.
To summarize, my question is how can I specify certain message-handlers to only apply to certain ApiController services instead of all of them. It seems that ASP.NET MVC 4 WebApi framework has over simplified the power and configurability of the Web Api Beta.
The recomended way to achieve this in the new Web API is with action filter attributes. They work pretty much the same way as in MVC, though you use a new set of base classes to implement them. The easiest way to get started is to derive from ActionFilterAttribute.
Related
Why does adding versioning to a webApi project removes number from the controller path name?
Replication steps :
Create a fresh .net6 project. And rename WeatherForecastController to WeatherForecast2Controller.
Run app and call https://localhost:x/WeatherForecast2 (where x is your port)
Observe valid/expected results
Add the below code in program.cs/startup
services.AddApiVersioning(config =>
{
config.AssumeDefaultVersionWhenUnspecified = true;
config.DefaultApiVersion = new ApiVersion(1, 0);
config.ReportApiVersions = true;
config.ApiVersionReader = ApiVersionReader.Combine(
new QueryStringApiVersionReader("version"),
new HeaderApiVersionReader("x-version"));
config.UseApiBehavior = false;
});
Run app and call https://localhost:x/WeatherForecast2 (where x is your port).
Step 5 will return a 404 not found error.
However if you call https://localhost:x/WeatherForecast. It will work.
So why does adding versioning, change the url path?
It's not entirely clear if you are using <= 5.0 or 6.0+.
History
The reason this behavior happens is because the only logical way to group controllers together is by their names. A controller name, therefore, becomes very important. This is problematic in code because two or more controllers in the same namespace cannot have the same type name. The assumption and long-defined convention has been to allow ASP.NET to remove the Controller suffix and then remove any remaining numbers. This allows ValuesController, Values2Controller and Values3Controller to all map to the logical controller name Values by default. In most cases, that's probably want someone wants. If API Versioning doesn't do this, then there is no way to collate all API versions together for an API.
Contrary to popular belief, route templates are not considered for grouping controllers (e.g. APIs). There is too much ambiguity as to how a template can map to code. Take the simplest example of two different versions of the same API with different route templates: V1 = values/{id}, V2 = values/{id:int}. These are semantically equivalent, but not the same. API Versioning does not try to understand what the route template means nor compare their equivalence. It can easily get a lot more complicated; especially, for overlapping route templates. For example, should order/{oid}/customer/{cid} be part of the Orders API or the Customer API? Only the service author knows for sure.
Regression
In the 5.0 release, a regression was accidentally introduced due to an over-optimization. The controller name is used in two places: the actual name of the controller and the name used to group controllers. It seems reasonable they'd be the same and why normalize (e.g. trim suffixes) more than necessary? It seemed like a good idea, but it caused unexpected behavior - such as this one. There are also legitimate reasons to have a number in the name of a controller; for example, S3Controller.
Fix
In library versions <= 5.0, developers had no control over the behavior of how names were normalized. In 5.1 and 6.0+, this is now exposed via the IControllerNameConvention service, which has two methods: one for normalizing the controller name and one for normalizing the group name. The following implementations are provided out of the box as properties on ControllerNameConvention:
Default: The default, out-of-the-box conventions
Original: The original names without any normalization (could result in the wrong behavior)
Grouped: The group name is normalized, but the controller name is unmodified
If none of those work for you, then you can create your own custom convention. In 5.1 this is wired up via ApiVersioningOptions.ControllerNameConvention, while in 6.0+ IControllerNameConvention is a transient service in the DI container.
Workaround
There are two ways you can workaround the problem using the current version you are leveraging:
Explicit Route Template
If you omit using the [controller] token, the routing problem will be resolved; for example, api/weatherforecast. You appear to have already discovered this.
Explicit Controller Name
The controller name is derived from a convention, even without API Versioning. It was understood this behavior could be a problem so API Versioning provides a way to explicit set it with the ControllerNameAttribute.
[ControllerName("WeatherForecast")]
[Route("api/[controller]")] // ← expands to 'api/WeatherForecast'
public class WeatherForecast2Controller : ControllerBase { }
Edge Case
This will solve the routing issues, but it will not fix the controller name issue. That should only matter if you are planning on documenting your API with OpenAPI (formerly Swagger). For example, S3Controller will simply show up as S, even though the route might be api/s3.
I think that your controller may have issues, as I used your AddApiVersioning code and it works for me. To avoid the conflicting action names, you can two different controllers.
ApiController
namespace WebApiVersioningApp.Controllers;
[ApiVersion("1.0")]
[ApiVersion("2.0")]
[ApiController]
[Route("api/Version2")]
public class Version2Controller : ControllerBase
{
[MapToApiVersion("1.0")]
[HttpGet(Name = "GetWeatherForecastV1")]
public string GetV1()
{
return "Version 1";
}
[MapToApiVersion("2.0")]
[HttpGet(Name = "GetWeatherForecastV2")]
public string GetV2()
{
return "Version 2";
}
}
Program:
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services.AddControllers();
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen(c =>
c.ResolveConflictingActions(apiDescriptions => apiDescriptions.First())
);
// Versioning setup
builder.Services.AddApiVersioning(o =>
{
o.ReportApiVersions = true;
o.AssumeDefaultVersionWhenUnspecified = true;
o.DefaultApiVersion = new ApiVersion(1, 0);
o.ApiVersionReader = ApiVersionReader.Combine(
new QueryStringApiVersionReader("version"),
new HeaderApiVersionReader("x-version"));
o.UseApiBehavior = false;
});
var app = builder.Build();
// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();
}
app.UseHttpsRedirection();
app.UseAuthorization();
app.MapControllers();
app.Run();
Hope this works for you.
In order to implement response caching, all that is needed to be done was:
inject .AddResponseCaching() into the services,
decorate a controller action with [ResponseCache(Duration = 10)].
Now I'm trying the minimal APIs that come with .NET 6 and I haven't figured out a way to do that except add the header myself cache-control: public,max-age=10.
Is there a more elegant way to do so?
ResponseCacheAttribute is part of MVC and as per docs will be applied to:
Razor Pages: Attributes can't be applied to handler methods.
MVC controllers.
MVC action methods: Method-level attributes override the settings specified in class-level attributes.
So it seems that adding cache header yourself is an only option. If you want you can "prettify" it a little bit using custom middleware. Something along this lines:
class CacheResponseMetadata
{
// add configuration properties if needed
}
class AddCacheHeadersMiddleware
{
private readonly RequestDelegate _next;
public AddCacheHeadersMiddleware(RequestDelegate next)
{
_next = next;
}
public async Task Invoke(HttpContext httpContext)
{
if (httpContext.GetEndpoint()?.Metadata.GetMetadata<CacheResponseMetadata>() is { } mutateResponseMetadata)
{
if (httpContext.Response.HasStarted)
{
throw new InvalidOperationException("Can't mutate response after headers have been sent to client.");
}
httpContext.Response.Headers.CacheControl = new[] { "public", "max-age=100" };
}
await _next(httpContext);
}
}
And usage:
app.UseMiddleware<AddCacheHeadersMiddleware>(); // optionally move to extension method
app.MapGet("/cache", () => $"Hello World, {Guid.NewGuid()}!")
.WithMetadata(new CacheResponseMetadata()); // optionally move to extension method
.NET 7 update
For .NET 7 you can configure output caching (see the linked article for differences with response caching):
The output caching middleware can be used in all types of ASP.NET Core apps: Minimal API, Web API with controllers, MVC, and Razor Pages.
Output caching can be done per endpoint via OutputCacheAttribute or CacheOutput method call:
app.MapGet("/cached", Gravatar.WriteGravatar).CacheOutput();
app.MapGet("/attribute", [OutputCache] (context) => Gravatar.WriteGravatar(context));
Or for multiple endpoints via policy:
The following code configures caching for all of the app's endpoints, with expiration time of 10 seconds.
builder.Services.AddOutputCache(options =>
{
options.AddBasePolicy(builder => builder.Expire(TimeSpan.FromSeconds(10)));
});
We have ASP.NET MVC 5 project, that we have plans to migrate to ASP.NET Core 3. Currently I gather dependency list and their equivalent replacements on the new new platform.
We are using MvcCodeRouting package to separate different workflows between different C# namespaces as was described in https://maxtoroq.github.io/2013/02/aspnet-mvc-workflow-per-controller.html.
Now with new platform in place, we need something similar. As last resort we could just specify all our namespaces in routing table, but I'd rather not to do so.
Any suggestions on how accomplish similar behavior?
EDIT:
I think with example it would be more understandable what I'm trying to accomplish.
We have following structure of controllers:
- Namespace1
-- Workflow1Controller.Index/Edit/Action
-- Workflow2Controller.Index/Edit/Action
-- Workflow3Controller.Index/Edit/Action
- Namespace2
-- Workflow4Controller.Index/Edit/Action
Workflow1Controller code:
namespace RootProjectNamespace.Controllers.Namespace1
{
class Workflow1Controller : Controller
{
public ActionResult Index() {}
// and so on
}
}
Appropriate Views are placed in similar manner.
And using MvcCodeRouting we able to create Action urls by following:
Url.Action("Namespace1.Workflow1Controller", "Index") // Creates -> ~/Namespace1/Workflow1Controller/Index
Url.Action("Namespace2.Workflow4Controller", "Action") // Creates -> ~/Namespace2/Workflow4Controller/Index
Is there possibility to achieve similar in .net core without explicit hardcoding routes in route table?
As an start point for your solution, you can create a custom IApplicationModelConvention and change the routing to use namespace in the routing.
There's an example in docs showing how you can do this. To learn more you can take a look at this great docs article: Work with the application model in ASP.NET Core.
To do so, first create the following NamespaceRoutingConvention class:
using Microsoft.AspNetCore.Mvc.ApplicationModels;
using System.Linq;
public class NamespaceRoutingConvention : IApplicationModelConvention
{
public void Apply(ApplicationModel application)
{
foreach (var controller in application.Controllers)
{
var hasAttributeRouteModels = controller.Selectors
.Any(selector => selector.AttributeRouteModel != null);
if (!hasAttributeRouteModels)
{
controller.Selectors[0].AttributeRouteModel = new AttributeRouteModel()
{
Template = controller.ControllerType.Namespace.Replace('.', '/')
+ "/[controller]/[action]/{id?}"
};
}
}
}
}
Then in startup, register the convention like this:
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc(options => {
options.Conventions.Add(new NamespaceRoutingConvention());
});
}
Then you can browse:
http://localhost:xxxxx/SampleProject/Controllers/Home/Index
I have recently come across this issue.
An MVC .NET Framework project I had been working on needed migrating to .NET Core, however it was made up from lots of smaller assemblies (DLLs), each containing different parts of the overall codebase.
MvcCodeRouting was used to implement this, and provided a way for the assemblies to be utilised, routed, and also provided the ability for the central (core) project to utilise the relevant views, stored within the assemblies.
I have found that the .NET Core "Application Parts" feature seems to fulfill most of these points, with it having the ability for projects to be seperated between different assemblies, imported into a main project, and utilised.
The only real functional difference in using this method is that each "Application Part" generates two assemblies instead of one, with one containing the controllers and module logic, whilst the other contains the (Razor) views (if the specific module has any).
I'm still experimenting with this, however I thought I'd add this here for the sake of completeness.
Below are some useful links to documentation regarding this feature:
https://learn.microsoft.com/en-us/aspnet/core/mvc/advanced/app-parts?view=aspnetcore-1.0#sample-generic-controller-feature
This is the main documentation for the feature within the ASP.NET Core documentation.
https://learn.microsoft.com/en-us/aspnet/core/razor-pages/ui-class?view=aspnetcore-1.0&tabs=visual-studio
This tutorial demonstrated creating an assembly containing only Razor Views, if some of the modules require no logic.
I'm developing an MVC API in a separate class library. The API methods use attribute routing. The API will be used by other MVC applications (not built by me).
The main MVC application will reference my library assembly and call AddMvc() / UseMvc() in it's own startup class. It will be able to set the root API url's for my API library dynamically (from configuration or options setup delegate), so that it can make sure there are no conflicts with it's own routes, which can use either attribute routing or centralized routing.
So let's say my API library has a product/{id} route. The main application should be able to choose any route prefix, like api/product/{id} or some/other/prefix/product/{id}.
At startup, MVC will discover all controllers/routes in all referenced assemblies, and it will also discover and register my API library routes, but only on the hardcoded product/{id} route without any prefix.
I've been trying to get MVC to register the routes with a prefix, but so far no success. The main application will call custom AddMyApi() / UseMyApi() config methods, so I can do configuration / setup for my library. Some of the things I tried:
Mapping
app.Map("/custom-prefix", api =>
{
api.UseMvc();
});
This will result in duplicate routes for both custom-prefix/product/{id} and product/{id}.
Route Convention
Based on http://www.strathweb.com/2016/06/global-route-prefix-with-asp-net-core-mvc-revisited/
services.AddMvc(options =>
{
options.Conventions.Insert(0, new RouteConvention(new RouteAttribute("custom-prefix")));
});
It looks like this will not work because the options will be overwritten by the main application's call to AddMvc(), or the other way around, depending which gets called first.
Custom route attribute
A custom route attribute based on IRouteTemplateProvider on the Controller classes will not work because I need the prefix injected from an options class, and attributes do not support constructor injection.
Postpone discovery of routes
Based on http://www.strathweb.com/2015/04/asp-net-mvc-6-discovers-controllers/
I've added [NonController] to the library controllers to prevent them being discovered at the main application's startup. However I've not been able to add them later, and also I suppose I will run into the same problem of the main application overwriting the MVC options again.
Areas
I can't use areas, because the main application may decide to run the API from the root (without prefix).
So I'm stuck as to how to solve this problem. Any help is appreciated.
I believe a convention is the right approach here and the bit you are missing is just providing the proper extension method for your library to be registered within MVC.
Start by creating a convention that will add a prefix to all controllers that pass a certain selector.
It is based on one I wrote for adding culture prefixes, but the idea is very similar to the article you linked.
Basically it will either update any existing AttributeRouteModel or add a new one if none is found.
This would be an example of such a convention:
public class ApiPrefixConvention: IApplicationModelConvention
{
private readonly string prefix;
private readonly Func<ControllerModel, bool> controllerSelector;
private readonly AttributeRouteModel onlyPrefixRoute;
private readonly AttributeRouteModel fullRoute;
public ApiPrefixConvention(string prefix, Func<ControllerModel, bool> controllerSelector)
{
this.prefix = prefix;
this.controllerSelector = controllerSelector;
// Prepare AttributeRouteModel local instances, ready to be added to the controllers
// This one is meant to be combined with existing route attributes
onlyPrefixRoute = new AttributeRouteModel(new RouteAttribute(prefix));
// This one is meant to be added as the route for api controllers that do not specify any route attribute
fullRoute = new AttributeRouteModel(
new RouteAttribute("api/[controller]"));
}
public void Apply(ApplicationModel application)
{
// Loop through any controller matching our selector
foreach (var controller in application.Controllers.Where(controllerSelector))
{
// Either update existing route attributes or add a new one
if (controller.Selectors.Any(x => x.AttributeRouteModel != null))
{
AddPrefixesToExistingRoutes(controller);
}
else
{
AddNewRoute(controller);
}
}
}
private void AddPrefixesToExistingRoutes(ControllerModel controller)
{
foreach (var selectorModel in controller.Selectors.Where(x => x.AttributeRouteModel != null).ToList())
{
// Merge existing route models with the api prefix
var originalAttributeRoute = selectorModel.AttributeRouteModel;
selectorModel.AttributeRouteModel =
AttributeRouteModel.CombineAttributeRouteModel(onlyPrefixRoute, originalAttributeRoute);
}
}
private void AddNewRoute(ControllerModel controller)
{
// The controller has no route attributes, lets add a default api convention
var defaultSelector = controller.Selectors.First(s => s.AttributeRouteModel == null);
defaultSelector.AttributeRouteModel = fullRoute;
}
}
Now, if this was all part of an app you are writing instead of a library, you would just register it as:
services.AddMvc(opts =>
{
var prefixConvention = new ApiPrefixConvention("api/", (c) => c.ControllerType.Namespace == "WebApplication2.Controllers.Api");
opts.Conventions.Insert(0, prefixConvention);
});
However since you are providing a library, what you want is to provide an extension method like AddMyLibrary("some/prefix") that will take care of adding this convention and any other setup like registering required services.
So you can write an extension method for IMvcBuilder and update the MvcOptions inside that method. The nice thing is that since is an extension of IMvcBuilder, it will always be called after the default AddMvc():
public static IMvcBuilder AddMyLibrary(this IMvcBuilder builder, string prefix = "api/")
{
// instantiate the convention with the right selector for your library.
// Check for namespace, marker attribute, name pattern, whatever your prefer
var prefixConvention = new ApiPrefixConvention(prefix, (c) => c.ControllerType.Namespace == "WebApplication2.Controllers.Api");
// Insert the convention within the MVC options
builder.Services.Configure<MvcOptions>(opts => opts.Conventions.Insert(0, prefixConvention));
// perform any extra setup required by your library, like registering services
// return builder so it can be chained
return builder;
}
Then you would ask users of your library to include it within their app as in:
services.AddMvc().AddMyLibrary("my/api/prefix/");
//Try this Reference enter link description here
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
app.UsePathBase("/Api/v/00");
app.Map("/api/v/0", api =>
{
api.UseMvc();
});
}
I'm working on a ASP.NET Web Api project and made it accept version information in the url.
For example:
api/v1/MyController api/v2/MyController
Now I would like to get the request version v1, v2 inside a custom LayoutRenderer for Nlog. Normally I would do this like the below example.
[LayoutRenderer("Version")]
public class VersionLayoutRenderer : LayoutRenderer
{
protected override void Append(System.Text.StringBuilder builder, NLog.LogEventInfo logEvent)
{
var version = HttpContext.Current.Request.RequestContext.RouteData.Values["Version"];
builder.Append(version);
}
}
The problem: HttpContext.Current is NULL
I believe this is because I use Async wrappers for NLog and some calls before the Logger are also Async.
A example of the logger being called Async inside Ninject.Extensions.WebApi.UsageLogger. At this point the HttpRequestMessage has all info we need to get the Version.
/// <summary>
/// Initializes a new instance of the <see cref="UsageHandler" /> class.
/// </summary>
public UsageHandler()
{
var kernel = new StandardKernel();
var logfactory = kernel.Get<ILoggerFactory>();
this.Log = logfactory.GetCurrentClassLogger();
}
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
var startTime = DateTime.Now;
// Log request
await request.Content.ReadAsStringAsync().ContinueWith(c =>
{
this.Log.Info("{0}: {1} called from {2}", request.Method, HttpUtility.UrlDecode(request.RequestUri.AbsoluteUri), ((HttpContextBase)request.Properties["MS_HttpContext"]).Request.UserHostAddress);
this.Log.Info("Content-Type: {0}, Content-Length: {1}", request.Content.Headers.ContentType != null ? request.Content.Headers.ContentType.MediaType : string.Empty, request.Content.Headers.ContentLength);
this.Log.Info("Accept-Encoding: {0}, Accept-Charset: {1}, Accept-Language: {2}", request.Headers.AcceptEncoding, request.Headers.AcceptCharset, request.Headers.AcceptLanguage);
if (!string.IsNullOrEmpty(c.Result))
{
if (this.MaxContentLength > 0 && c.Result.Length > this.MaxContentLength)
{
this.Log.Info("Data: {0}", HttpUtility.UrlDecode(c.Result).Substring(0, this.MaxContentLength - 1));
}
else
{
this.Log.Info("Data: {0}", HttpUtility.UrlDecode(c.Result));
}
}
});
var response = await base.SendAsync(request, cancellationToken);
// Log the error if it returned an error
if (!response.IsSuccessStatusCode)
{
this.Log.Error(response.Content.ReadAsStringAsync().Result);
}
// Log performance
this.Log.Info("Request processing time: " + DateTime.Now.Subtract(startTime).TotalSeconds + "s");
return response;
}
The question
What would be the best way to make the VersionLayoutRenderer work in a generic way? Could I add a MessageHandler and Bind the HttpRequest to some Async scope? If so any guidelines would be much appreciated cause I'm still getting used to Ninject.
For the time being I add the version information directly to the Log Call in the UsageHandler, but I would really like a more generic solution, where I can always rely on version information inside my logging.
Edit: Updated the question to be more specific and included more details.
Try injecting the context using something like:
kernel.Bind<IDependency>()
.To<Mydependency>()
.InRequestScope()
.WithConstructorArgument("context",c=>HttpContext.Current);
The actual issue is really neutral wrt what you should do with Ninject - you just need to get the phasing of your processing such that any objects that are going be running async have everything they need without relying on the magic HttpContext.Current. Get that working with no DI Container first.
Then, to use Ninject the major steps are:-
Your Bind statements need to be run once. See the Ninject.MV3 wiki for the best approach (until it gets merged in, there is not OOTB with the NuGet-based edition)
as #rickythefox (+1'd) says, your registration should bake the thread/context-relative data into the object and you config the registration such that it can happen early in request processing, when you're still on the thread that's HttpContext.Current
kernel.Bind<ILogger>()
// TODO replace GCCL with something like GetClassLogger(ctx.Request.Service.ReflectedType) - see the wiki for examples
.ToMethod( ctx=> ctx.Get<ILoggerFactory>().GetCurrentClassLogger())
.InRequestScope()
.WithConstructorArgument("context",c=>HttpContext.Current);
Then just make the constructor of the handler take a ILogger, which can be assigned to .Log (which I hope isnt static :D)
NB, the aim is for you never to write a kernel.Get(), ever, period.
The real problem here though, is that proper use of WebApi does not involve using HttpContext.Current or any other magic static methods or anything similar (for testability, to make yourself independent of the hosting context (self hosting, OWIN etc), and many more reasons).
Also, if you are using NLog (or Log4Net) you should also look at the Ninject.Extensions.Logging package (and source).
The GlobalConfiguration class can provide you access to the routing configuration.
// The code below assumes a map routing convention of api/{version}/{controller}/....
// This will give you the configured routes
var routes = GlobalConfiguration.Configuration.Routes;
// This will give you the route templates
var templates = routes
.Select(route => route.RouteTemplate);
// This will give you the distinct versions for all controllers
var versions = routes
.Select(route => route.RouteTemplate)
.Select(template => template.Split("/".ToCharArray(), StringSplitOptions.RemoveEmptyEntries))
.Select(values => values[1])
.Distinct();
// This will give you the distinct versions for a controller with the specified name
var name = "MyController";
var controllerVersions = routes
.Select(route => route.RouteTemplate)
.Select(template => template.Split("/".ToCharArray(), StringSplitOptions.RemoveEmptyEntries))
.Where(values => String.Equals(values[2], name, StringComparison.OrdinalIgnoreCase))
.Select(values => values[1])
.Distinct();
I am not sure if you are trying to resolve the version with a known value (the name of the controller) or if you are trying to dynamically resolve it. If you inject the current HttpContext, you can use the request URI of the context to filter the routes via the route template.
Edit: After your comments I realized the routing configuration isn't what you were after.
If the end goal is to implement logging within your controllers, you may want to take a look at Tracing in ASP.NET Web API as there is support for tracing built-in to the Web API infrastructure.