是否可以 select 基于接受的媒体类型 header 在 .NET MVC 中使用 AttributeRouting 的操作?

Is it possible to select an Action with AttributeRouting in .NET MVC based on the Media Type of the Accept header?

我想 select 我的控制器基于接受中请求的媒体类型的操作 header。

例如,我有一个名为主题的资源。它的指定路线是:

GET /subjects/{subjectId:int}

正常情况下,浏览器正在请求 text/html,这很好。默认的 Media Formatter 处理得很好。

现在,当使用指定 application/pdf 作为接受的媒体类型的接受 header 访问同一路由时,我有自定义逻辑要执行。

我可以创建一个自定义的媒体格式化程序,但是,据我了解,这意味着任何请求的 Accept header 设置为 application/pdf 的路由也会 运行通过这个媒体格式化程序。这是不可接受的。

在Java中,有一个注解叫做@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.

这将允许我执行以下操作:

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.
        }
    }
}

当然,我找不到 .NET 中的 Produces 属性,所以这不起作用。我也没能找到类似的属性。

我当然可以在操作的 body 中手动检查 header,并将其重定向到另一个操作,但这充其量看起来很老套。

.NET 4.5 中是否有我可以用来解决我忽略或遗漏的问题的机制?

(我正在使用 NuGet 存储库中的 MVC 5.2.2)

在 Internet 上搜索了一段时间后,我想出最好的办法是创建一个 ActionMethodSelectorAttribute

以下是我编写的 ProducesAttribute 的一个非常幼稚的首次实施,最终目的是模仿 Java 的 Produces 注释:

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;
        }
    }
}

它旨在与属性路由一起使用,可以按如下方式应用:

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.
    }
}