I want to select an Action of my Controller based on the Media Type requested in the Accept header.
For example, I have a resource called a subject. Its assigned route is:
GET /subjects/{subjectId:int}
Normally, the browser is requesting text/html
, which is fine. The default Media Formatter handles this great.
Now, I have custom logic I want to perform when this same route is accessed with an accept header specifying application/pdf
as the accepted Media Type.
I could create a custom Media Formatter, but, to my understanding, this would mean that any route that is requested with the Accept header set to application/pdf
would also run through this Media Formatter. This is unacceptable.
In Java, there is an annotation called @Produces
:
The @Produces annotation is used to specify the MIME media types or representations a resource can produce and send back to the client. If @Produces is applied at the class level, all the methods in a resource can produce the specified MIME types by default. If applied at the method level, the annotation overrides any @Produces annotations applied at the class level.
This would allow me to do the following:
namespace MyNamespace
{
[RoutePrefix("subjects")]
public class SubjectsController : Controller
{
[Route("{subjectId:int}")]
[HttpGet]
public ActionResult GetSubject(int subjectId)
{
}
[Route("{subjectId:int}")]
[HttpGet]
[Produces("application/pdf")]
public ActionResult GetSubjectAsPdf(int subjectId)
{
//Run my custom logic here to generate a PDF.
}
}
}
There is no Produces Attribute in .NET that I can find, of course, so this doesn't work. I haven't been able to find a similar attribute, either.
I could of course manually check the header within the body of the action, and redirect it to another action, but that seems hackish at best.
Is there a mechanism in .NET 4.5 that I may use to pull this off that I'm overlooking or missing?
(I'm using MVC 5.2.2 from NuGet repository)
After searching around the Internet for awhile, I came up with the idea that this would be best accomplished by creating an ActionMethodSelectorAttribute
.
The following is a very naive, first-pass implementation of a ProducesAttribute
that I wrote with the eventual intent of mimicking Java's Produces annotation:
namespace YourNamespace
{
using System;
using System.Collections.Generic;
using System.Net;
using System.Net.Mime;
using System.Web.Mvc;
[AttributeUsage(AttributeTargets.Method, AllowMultiple = false, Inherited = true)]
public sealed class ProducesAttribute : ActionMethodSelectorAttribute
{
private readonly ISet<ContentType> acceptableMimeTypes;
public ProducesAttribute(params string[] acceptableMimeTypes)
{
this.acceptableMimeTypes = new HashSet<ContentType>();
foreach (string acceptableMimeType in acceptableMimeTypes)
this.acceptableMimeTypes.Add(new ContentType(acceptableMimeType));
}
public override bool IsValidForRequest(ControllerContext controllerContext, System.Reflection.MethodInfo methodInfo)
{
string acceptHeader = controllerContext.RequestContext.HttpContext.Request.Headers[HttpRequestHeader.Accept.ToString()];
string[] headerMimeTypes = acceptHeader.Split(new char[] {','}, StringSplitOptions.RemoveEmptyEntries);
foreach (var headerMimeType in headerMimeTypes)
{
if (this.acceptableMimeTypes.Contains(new ContentType(headerMimeType)))
return true;
}
return false;
}
}
}
It is meant to be used with Attribute Routing, and can be applied as follows:
public sealed class MyController : Controller
{
[Route("subjects/{subjectId:int}")] //My route
[Produces("application/pdf")]
public ActionResult GetSubjectAsPdf(int subjectId)
{
//Here you would return the PDF representation.
}
[Route("subjects/{subjectId:int}")]
public ActionResult GetSubject(int subjectId)
{
//Would handle all other routes.
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With