Changing produces and format filter interaction
This commit is contained in:
parent
7ee80020e1
commit
addd8dd5d2
|
|
@ -5,7 +5,6 @@ using Microsoft.AspNet.Mvc;
|
||||||
|
|
||||||
namespace MvcSample.Web.Controllers
|
namespace MvcSample.Web.Controllers
|
||||||
{
|
{
|
||||||
[FormatFilter]
|
|
||||||
public class FormatFilterController : Controller
|
public class FormatFilterController : Controller
|
||||||
{
|
{
|
||||||
public Product GetProduct(int id)
|
public Product GetProduct(int id)
|
||||||
|
|
|
||||||
|
|
@ -64,8 +64,7 @@ namespace MvcSample.Web
|
||||||
{
|
{
|
||||||
options.Filters.Add(typeof(PassThroughAttribute), order: 17);
|
options.Filters.Add(typeof(PassThroughAttribute), order: 17);
|
||||||
options.AddXmlDataContractSerializerFormatter();
|
options.AddXmlDataContractSerializerFormatter();
|
||||||
var formatFilter = new FormatFilterAttribute();
|
options.Filters.Add(new FormatFilterAttribute());
|
||||||
options.Filters.Add(formatFilter);
|
|
||||||
});
|
});
|
||||||
services.Configure<RazorViewEngineOptions>(options =>
|
services.Configure<RazorViewEngineOptions>(options =>
|
||||||
{
|
{
|
||||||
|
|
@ -108,9 +107,7 @@ namespace MvcSample.Web
|
||||||
{
|
{
|
||||||
options.Filters.Add(typeof(PassThroughAttribute), order: 17);
|
options.Filters.Add(typeof(PassThroughAttribute), order: 17);
|
||||||
options.AddXmlDataContractSerializerFormatter();
|
options.AddXmlDataContractSerializerFormatter();
|
||||||
|
options.Filters.Add(new FormatFilterAttribute());
|
||||||
var formatFilter = new FormatFilterAttribute();
|
|
||||||
options.Filters.Add(formatFilter);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -13,19 +13,18 @@ using System.Collections.Generic;
|
||||||
namespace Microsoft.AspNet.Mvc
|
namespace Microsoft.AspNet.Mvc
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// This will look at the format parameter if present in the route data or query data and sets the content type in
|
/// A filter which will use the format value in the route data or query string to set the content type on an
|
||||||
/// ObjectResult corresponding to the format value.
|
/// <see cref="ObjectResult" /> returned from an action.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = true)]
|
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = true)]
|
||||||
public class FormatFilterAttribute : Attribute, IFormatFilter, IResourceFilter, IResultFilter
|
public class FormatFilterAttribute : Attribute, IFormatFilter, IResourceFilter, IResultFilter
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// As a resourceFilter, this filter looks at the request and rejects it
|
/// As a <see cref="IResourceFilter"/>, this filter looks at the request and rejects it before going ahead if
|
||||||
/// before going ahead if
|
|
||||||
/// 1. The format in the request doesnt match any format in the map.
|
/// 1. The format in the request doesnt match any format in the map.
|
||||||
/// 2. If there is a conflicting producesFilter.
|
/// 2. If there is a conflicting producesFilter.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="context"></param>
|
/// <param name="context">The <see cref="ResourceExecutingContext"/>.</param>
|
||||||
public void OnResourceExecuting([NotNull] ResourceExecutingContext context)
|
public void OnResourceExecuting([NotNull] ResourceExecutingContext context)
|
||||||
{
|
{
|
||||||
var format = GetFormat(context);
|
var format = GetFormat(context);
|
||||||
|
|
@ -41,16 +40,18 @@ namespace Microsoft.AspNet.Mvc
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
var responseTypeFilters = context.Filters.OfType<IApiResponseMetadataProvider>();
|
var responseTypeFilters = context.Filters.OfType<IApiResponseMetadataProvider>();
|
||||||
if (responseTypeFilters.Count() != 0)
|
var contentTypes = new List<MediaTypeHeaderValue>();
|
||||||
|
|
||||||
|
foreach (var filter in responseTypeFilters)
|
||||||
{
|
{
|
||||||
var contentTypes = new List<MediaTypeHeaderValue>();
|
filter.SetContentTypes(contentTypes);
|
||||||
|
}
|
||||||
|
|
||||||
foreach (var filter in responseTypeFilters)
|
if (contentTypes.Count() != 0)
|
||||||
{
|
{
|
||||||
filter.SetContentTypes(contentTypes);
|
// If formatfilterContentType is not subset of any of the content types produced by
|
||||||
}
|
// IApiResponseMetadataProviders, return 404
|
||||||
|
if (!contentTypes.Any(c => formatContentType.IsSubsetOf(c)))
|
||||||
if (!contentTypes.Any(c => c.IsSubsetOf(formatContentType)))
|
|
||||||
{
|
{
|
||||||
context.Result = new HttpNotFoundResult();
|
context.Result = new HttpNotFoundResult();
|
||||||
}
|
}
|
||||||
|
|
@ -66,19 +67,19 @@ namespace Microsoft.AspNet.Mvc
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// This method looks at the format that request has and sets it in the context
|
/// Sets a Content Type on an <see cref="ObjectResult" /> using a format value from the request.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
/// <param name="context">The <see cref="ResultExecutingContext"/>.</param>
|
||||||
public void OnResultExecuting([NotNull] ResultExecutingContext context)
|
public void OnResultExecuting([NotNull] ResultExecutingContext context)
|
||||||
{
|
{
|
||||||
var format = GetFormat(context);
|
var format = GetFormat(context);
|
||||||
if (format != null)
|
if (!string.IsNullOrEmpty(format))
|
||||||
{
|
{
|
||||||
var contentType = GetContentType(format, context);
|
|
||||||
Debug.Assert(contentType != null);
|
|
||||||
|
|
||||||
var objectResult = context.Result as ObjectResult;
|
var objectResult = context.Result as ObjectResult;
|
||||||
if (objectResult != null)
|
if (objectResult != null)
|
||||||
{
|
{
|
||||||
|
var contentType = GetContentType(format, context);
|
||||||
|
Debug.Assert(contentType != null);
|
||||||
objectResult.ContentTypes.Clear();
|
objectResult.ContentTypes.Clear();
|
||||||
objectResult.ContentTypes.Add(contentType);
|
objectResult.ContentTypes.Add(contentType);
|
||||||
}
|
}
|
||||||
|
|
@ -92,20 +93,15 @@ namespace Microsoft.AspNet.Mvc
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Looks at the current request for the format parameter. If it contains format, it returns the content type
|
/// If the current request contains format value, returns true. It means the format filter is going to execute.
|
||||||
/// for it.
|
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="context"></param>
|
/// <param name="context">The <see cref="FilterContext"/></param>
|
||||||
/// <returns></returns>
|
/// <returns>If the filter is active and will execute.</returns>
|
||||||
public MediaTypeHeaderValue GetContentTypeForCurrentRequest(FilterContext context)
|
public bool IsActive(FilterContext context)
|
||||||
{
|
{
|
||||||
var format = GetFormat(context);
|
var format = GetFormat(context);
|
||||||
if (format != null && !string.IsNullOrEmpty(format.ToString()))
|
|
||||||
{
|
|
||||||
return GetContentType(format, context);
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
return !string.IsNullOrEmpty(format);
|
||||||
}
|
}
|
||||||
|
|
||||||
private string GetFormat(FilterContext context)
|
private string GetFormat(FilterContext context)
|
||||||
|
|
@ -114,25 +110,17 @@ namespace Microsoft.AspNet.Mvc
|
||||||
|
|
||||||
if (!context.RouteData.Values.TryGetValue("format", out format))
|
if (!context.RouteData.Values.TryGetValue("format", out format))
|
||||||
{
|
{
|
||||||
if (context.HttpContext.Request.Query.ContainsKey("format"))
|
format = context.HttpContext.Request.Query["format"];
|
||||||
{
|
|
||||||
format = context.HttpContext.Request.Query.Get("format");
|
|
||||||
return format.ToString();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
return format.ToString();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return (string)format;
|
||||||
}
|
}
|
||||||
|
|
||||||
private MediaTypeHeaderValue GetContentType(object format, FilterContext context)
|
private MediaTypeHeaderValue GetContentType(string format, FilterContext context)
|
||||||
{
|
{
|
||||||
Debug.Assert(format != null);
|
|
||||||
var options = context.HttpContext.RequestServices.GetRequiredService<IOptions<MvcOptions>>();
|
var options = context.HttpContext.RequestServices.GetRequiredService<IOptions<MvcOptions>>();
|
||||||
var contentType = options.Options.FormatterMappings.GetMediaTypeForFormat(format.ToString());
|
var contentType = options.Options.FormatterMappings.GetMediaTypeMappingForFormat(format);
|
||||||
|
|
||||||
return contentType;
|
return contentType;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,14 +7,17 @@ using System;
|
||||||
namespace Microsoft.AspNet.Mvc
|
namespace Microsoft.AspNet.Mvc
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Implement this interface if you want to have your own implementation of FormatFilter. A FormatFilter decides
|
/// A filter which produces a desired content type for the current request.
|
||||||
/// what content type to use if the format is present in the Url.
|
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
/// <remarks>A FormatFilter decides what content type to use if a format value is present in the URL.
|
||||||
|
/// </remarks>
|
||||||
public interface IFormatFilter : IFilter
|
public interface IFormatFilter : IFilter
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Get the <see cref="MediaTypeHeaderValue"/> registered fot the format in the request.
|
/// Returns true if the filter is going to be executed for the current request.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
MediaTypeHeaderValue GetContentTypeForCurrentRequest(FilterContext context);
|
/// <param name="context">The <see cref="FilterContext"/></param>
|
||||||
|
/// <returns>If the filter will execute</returns>
|
||||||
|
bool IsActive(FilterContext context);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -34,10 +34,9 @@ namespace Microsoft.AspNet.Mvc
|
||||||
|
|
||||||
if (objectResult != null)
|
if (objectResult != null)
|
||||||
{
|
{
|
||||||
// Check if FormatFilter has already set the content type
|
// Check if there are any IFormatFilter in the pipeline, and if any of them is active. If there is one,
|
||||||
// If it has, dont override it
|
// do not override the content type value.
|
||||||
var formatFilter = context.Filters.OfType<IFormatFilter>().LastOrDefault();
|
if (context.Filters.OfType<IFormatFilter>().All(f => !f.IsActive(context)))
|
||||||
if (formatFilter == null || formatFilter.GetContentTypeForCurrentRequest(context) == null)
|
|
||||||
{
|
{
|
||||||
SetContentTypes(objectResult.ContentTypes);
|
SetContentTypes(objectResult.ContentTypes);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,7 @@ using Microsoft.AspNet.Mvc.Core;
|
||||||
namespace Microsoft.AspNet.Mvc
|
namespace Microsoft.AspNet.Mvc
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Used to specify mapping between the Url Format and corresponding <see cref="MediaTypeHeaderValue"/>.
|
/// Used to specify mapping between the URL Format and corresponding <see cref="MediaTypeHeaderValue"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class FormatterMappings
|
public class FormatterMappings
|
||||||
{
|
{
|
||||||
|
|
@ -19,21 +19,14 @@ namespace Microsoft.AspNet.Mvc
|
||||||
new Dictionary<string, MediaTypeHeaderValue>(StringComparer.OrdinalIgnoreCase);
|
new Dictionary<string, MediaTypeHeaderValue>(StringComparer.OrdinalIgnoreCase);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// This will set mapping for the format to specified <see cref="MediaTypeHeaderValue"/>.
|
/// Sets mapping for the format to specified <see cref="MediaTypeHeaderValue"/>.
|
||||||
/// If the format already exists, the <see cref="MediaTypeHeaderValue"/> will be overwritten with the new value.
|
/// If the format already exists, the <see cref="MediaTypeHeaderValue"/> will be overwritten with the new value.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
/// <param name="format">format value</param>
|
||||||
|
/// <param name="contentType">The <see cref="MediaTypeHeaderValue"/> for the format value</param>
|
||||||
public void SetMediaTypeMappingForFormat([NotNull] string format, [NotNull] MediaTypeHeaderValue contentType)
|
public void SetMediaTypeMappingForFormat([NotNull] string format, [NotNull] MediaTypeHeaderValue contentType)
|
||||||
{
|
{
|
||||||
if (string.IsNullOrEmpty(format))
|
ValidateContentType(contentType);
|
||||||
{
|
|
||||||
throw new ArgumentException(Resources.ArgumentCannotBeNullOrEmpty, "format");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (contentType == null)
|
|
||||||
{
|
|
||||||
throw new ArgumentException((Resources.ArgumentCannotBeNullOrEmpty), "contentType");
|
|
||||||
}
|
|
||||||
|
|
||||||
format = RemovePeriodIfPresent(format);
|
format = RemovePeriodIfPresent(format);
|
||||||
_map[format] = contentType;
|
_map[format] = contentType;
|
||||||
}
|
}
|
||||||
|
|
@ -41,14 +34,26 @@ namespace Microsoft.AspNet.Mvc
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets <see cref="MediaTypeHeaderValue"/> for the specified format.
|
/// Gets <see cref="MediaTypeHeaderValue"/> for the specified format.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public MediaTypeHeaderValue GetMediaTypeForFormat(string format)
|
/// <param name="format">format value.</param>
|
||||||
|
/// <returns>The <see cref="MediaTypeHeaderValue"/> for input format</returns>
|
||||||
|
public MediaTypeHeaderValue GetMediaTypeMappingForFormat([NotNull] string format)
|
||||||
{
|
{
|
||||||
format = RemovePeriodIfPresent(format);
|
format = RemovePeriodIfPresent(format);
|
||||||
|
|
||||||
MediaTypeHeaderValue value = null;
|
MediaTypeHeaderValue value = null;
|
||||||
_map.TryGetValue(format, out value);
|
_map.TryGetValue(format, out value);
|
||||||
|
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void ValidateContentType(MediaTypeHeaderValue contentType)
|
||||||
|
{
|
||||||
|
if(contentType.Type == "*" || contentType.SubType == "*")
|
||||||
|
{
|
||||||
|
throw new ArgumentException(string.Format(Resources.FormatterMappings_NotValidMediaType, contentType));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private string RemovePeriodIfPresent(string format)
|
private string RemovePeriodIfPresent(string format)
|
||||||
{
|
{
|
||||||
if (format.StartsWith("."))
|
if (format.StartsWith("."))
|
||||||
|
|
|
||||||
|
|
@ -55,7 +55,7 @@ namespace Microsoft.AspNet.Mvc
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Used to specify mapping between the Url Format and corresponding <see cref="MediaTypeHeaderValue"/>.
|
/// Used to specify mapping between the URL Format and corresponding <see cref="MediaTypeHeaderValue"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public FormatterMappings FormatterMappings { get; }
|
public FormatterMappings FormatterMappings { get; }
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -562,6 +562,11 @@ namespace Microsoft.AspNet.Mvc.Core
|
||||||
get { return GetString("Common_ValueNotValidForProperty"); }
|
get { return GetString("Common_ValueNotValidForProperty"); }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal static string FormatterMappings_NotValidMediaType
|
||||||
|
{
|
||||||
|
get { return GetString("FormatterMappings_NotValidMediaType"); }
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The value '{0}' is invalid.
|
/// The value '{0}' is invalid.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
|
||||||
|
|
@ -450,6 +450,9 @@
|
||||||
</data>
|
</data>
|
||||||
<data name="ApiExplorer_UnsupportedAction" xml:space="preserve">
|
<data name="ApiExplorer_UnsupportedAction" xml:space="preserve">
|
||||||
<value>The action '{0}' has ApiExplorer enabled, but is using conventional routing. Only actions which use attribute routing support ApiExplorer.</value>
|
<value>The action '{0}' has ApiExplorer enabled, but is using conventional routing. Only actions which use attribute routing support ApiExplorer.</value>
|
||||||
|
</data>
|
||||||
|
<data name="FormatterMappings_NotValidMediaType" xml:space="preserve">
|
||||||
|
<value>The media type {0} is not valid. The media type containing "<mediatype>/*" are not supported.</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="RemoteAttribute_NoUrlFound" xml:space="preserve">
|
<data name="RemoteAttribute_NoUrlFound" xml:space="preserve">
|
||||||
<value>No URL for remote validation could be found.</value>
|
<value>No URL for remote validation could be found.</value>
|
||||||
|
|
|
||||||
|
|
@ -188,16 +188,13 @@ namespace Microsoft.AspNet.Mvc
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void FormatFilter_ContextContainsFormat_ContainsProducesFilter_WildCardMatching()
|
public void FormatFilter_LessSpecificThan_Produces()
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var produces = new ProducesAttribute(
|
var produces = new ProducesAttribute("application/xml;version=1", new string [] { });
|
||||||
"application/baz",
|
var context = CreateResourceExecutingContext(new IFilter[] { produces }, "xml", FormatSource.RouteData);
|
||||||
new string[] { "application/foo", "text/bar" });
|
|
||||||
var context = CreateResourceExecutingContext(new IFilter[] { produces }, "star", FormatSource.RouteData);
|
|
||||||
var options = context.HttpContext.RequestServices.GetService<IOptions<MvcOptions>>();
|
var options = context.HttpContext.RequestServices.GetService<IOptions<MvcOptions>>();
|
||||||
options.Options.FormatterMappings.SetMediaTypeMappingForFormat("star", MediaTypeHeaderValue.Parse("application/*"));
|
options.Options.FormatterMappings.SetMediaTypeMappingForFormat("xml", MediaTypeHeaderValue.Parse("application/xml"));
|
||||||
|
|
||||||
var filter = new FormatFilterAttribute();
|
var filter = new FormatFilterAttribute();
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
|
|
@ -207,6 +204,24 @@ namespace Microsoft.AspNet.Mvc
|
||||||
Assert.Null(context.Result);
|
Assert.Null(context.Result);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void FormatFilter_MoreSpecificThan_Produces()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var produces = new ProducesAttribute("application/xml", new string[] { });
|
||||||
|
var context = CreateResourceExecutingContext(new IFilter[] { produces }, "xml", FormatSource.RouteData);
|
||||||
|
var options = context.HttpContext.RequestServices.GetService<IOptions<MvcOptions>>();
|
||||||
|
options.Options.FormatterMappings.SetMediaTypeMappingForFormat("xml", MediaTypeHeaderValue.Parse("application/xml;version=1"));
|
||||||
|
var filter = new FormatFilterAttribute();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
filter.OnResourceExecuting(context);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
var actionResult = context.Result;
|
||||||
|
Assert.IsType<HttpNotFoundResult>(actionResult);
|
||||||
|
}
|
||||||
|
|
||||||
[Theory]
|
[Theory]
|
||||||
[InlineData("json", FormatSource.RouteData, "application/json")]
|
[InlineData("json", FormatSource.RouteData, "application/json")]
|
||||||
[InlineData("json", FormatSource.QueryData, "application/json")]
|
[InlineData("json", FormatSource.QueryData, "application/json")]
|
||||||
|
|
@ -241,7 +256,7 @@ namespace Microsoft.AspNet.Mvc
|
||||||
var resourceExecutingContext = CreateResourceExecutingContext(
|
var resourceExecutingContext = CreateResourceExecutingContext(
|
||||||
new IFilter[] { },
|
new IFilter[] { },
|
||||||
format,
|
format,
|
||||||
FormatSource.RouteData);
|
place);
|
||||||
var filter = new FormatFilterAttribute();
|
var filter = new FormatFilterAttribute();
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
|
|
@ -252,31 +267,24 @@ namespace Microsoft.AspNet.Mvc
|
||||||
}
|
}
|
||||||
|
|
||||||
[Theory]
|
[Theory]
|
||||||
[InlineData("json", FormatSource.RouteData, "application/json")]
|
[InlineData("json", FormatSource.RouteData, true)]
|
||||||
[InlineData("json", FormatSource.QueryData, "application/json")]
|
[InlineData("json", FormatSource.QueryData, true )]
|
||||||
[InlineData("", FormatSource.RouteAndQueryData, null)]
|
[InlineData("", FormatSource.RouteAndQueryData, false)]
|
||||||
[InlineData(null, FormatSource.RouteAndQueryData, null)]
|
[InlineData(null, FormatSource.RouteAndQueryData, false)]
|
||||||
public void FormatFilter_GetContentTypeForRequest(
|
public void FormatFilter_IsActive(
|
||||||
string format,
|
string format,
|
||||||
FormatSource place,
|
FormatSource place,
|
||||||
string contentType)
|
bool expected)
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var resourceExecutingContext = CreateResourceExecutingContext(
|
var resultExecutingContext = CreateResultExecutingContext(format, place);
|
||||||
new IFilter[] { },
|
|
||||||
format,
|
|
||||||
FormatSource.RouteData);
|
|
||||||
var filter = new FormatFilterAttribute();
|
var filter = new FormatFilterAttribute();
|
||||||
var returnContentType = filter.GetContentTypeForCurrentRequest(resourceExecutingContext);
|
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var isActive = filter.IsActive(resultExecutingContext);
|
||||||
|
|
||||||
MediaTypeHeaderValue mediaType = null;
|
// Assert
|
||||||
if (returnContentType != null)
|
Assert.Equal(expected, isActive);
|
||||||
{
|
|
||||||
mediaType = MediaTypeHeaderValue.Parse("application/json");
|
|
||||||
}
|
|
||||||
|
|
||||||
Assert.Equal(mediaType, returnContentType);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static ResourceExecutingContext CreateResourceExecutingContext(
|
private static ResourceExecutingContext CreateResourceExecutingContext(
|
||||||
|
|
@ -332,7 +340,7 @@ namespace Microsoft.AspNet.Mvc
|
||||||
if (place == FormatSource.QueryData || place == FormatSource.RouteAndQueryData)
|
if (place == FormatSource.QueryData || place == FormatSource.RouteAndQueryData)
|
||||||
{
|
{
|
||||||
httpContext.Setup(c => c.Request.Query.ContainsKey("format")).Returns(true);
|
httpContext.Setup(c => c.Request.Query.ContainsKey("format")).Returns(true);
|
||||||
httpContext.Setup(c => c.Request.Query.Get("format")).Returns(format);
|
httpContext.Setup(c => c.Request.Query["format"]).Returns(format);
|
||||||
}
|
}
|
||||||
else if (place == null && format == null)
|
else if (place == null && format == null)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -39,16 +39,38 @@ namespace Microsoft.AspNet.Mvc.Test
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task ProducesContentAttribute_FormatFilterAttribute()
|
public async Task ProducesContentAttribute_FormatFilterAttribute_NotActive()
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var mediaType1 = MediaTypeHeaderValue.Parse("application/xml");
|
|
||||||
var mediaType2 = MediaTypeHeaderValue.Parse("application/json");
|
|
||||||
var producesContentAttribute = new ProducesAttribute("application/xml");
|
var producesContentAttribute = new ProducesAttribute("application/xml");
|
||||||
|
|
||||||
var formatFilter = new Mock<IFormatFilter>();
|
var formatFilter = new Mock<IFormatFilter>();
|
||||||
formatFilter.Setup(f => f.GetContentTypeForCurrentRequest(It.IsAny<FilterContext>()))
|
formatFilter.Setup(f => f.IsActive(It.IsAny<FilterContext>()))
|
||||||
.Returns(mediaType2);
|
.Returns(false);
|
||||||
|
|
||||||
|
var filters = new IFilter[] { producesContentAttribute, formatFilter.Object };
|
||||||
|
var resultExecutingContext = CreateResultExecutingContext(filters);
|
||||||
|
|
||||||
|
var next = new ResultExecutionDelegate(
|
||||||
|
() => Task.FromResult(CreateResultExecutedContext(resultExecutingContext)));
|
||||||
|
|
||||||
|
// Act
|
||||||
|
await producesContentAttribute.OnResultExecutionAsync(resultExecutingContext, next);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
var objectResult = Assert.IsType<ObjectResult>(resultExecutingContext.Result);
|
||||||
|
Assert.Equal(1, objectResult.ContentTypes.Count);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task ProducesContentAttribute_FormatFilterAttribute_Active()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var producesContentAttribute = new ProducesAttribute("application/xml");
|
||||||
|
|
||||||
|
var formatFilter = new Mock<IFormatFilter>();
|
||||||
|
formatFilter.Setup(f => f.IsActive(It.IsAny<FilterContext>()))
|
||||||
|
.Returns(true);
|
||||||
|
|
||||||
var filters = new IFilter[] { producesContentAttribute, formatFilter.Object };
|
var filters = new IFilter[] { producesContentAttribute, formatFilter.Object };
|
||||||
var resultExecutingContext = CreateResultExecutingContext(filters);
|
var resultExecutingContext = CreateResultExecutingContext(filters);
|
||||||
|
|
@ -64,7 +86,6 @@ namespace Microsoft.AspNet.Mvc.Test
|
||||||
Assert.Equal(0, objectResult.ContentTypes.Count);
|
Assert.Equal(0, objectResult.ContentTypes.Count);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
[Theory]
|
[Theory]
|
||||||
[InlineData("", "")]
|
[InlineData("", "")]
|
||||||
[InlineData("application/xml,, application/json", "")]
|
[InlineData("application/xml,, application/json", "")]
|
||||||
|
|
|
||||||
|
|
@ -24,31 +24,25 @@ namespace Microsoft.AspNet.Mvc
|
||||||
options.SetMediaTypeMappingForFormat(setFormat, mediaType);
|
options.SetMediaTypeMappingForFormat(setFormat, mediaType);
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var returnMediaType = options.GetMediaTypeForFormat(getFormat);
|
var returnMediaType = options.GetMediaTypeMappingForFormat(getFormat);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.Equal(mediaType, returnMediaType);
|
Assert.Equal(mediaType, returnMediaType);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Theory]
|
[Theory]
|
||||||
[InlineData("xml", null)]
|
[InlineData("application/*")]
|
||||||
[InlineData(".json", null)]
|
[InlineData("*/json")]
|
||||||
[InlineData(null, "application/json")]
|
[InlineData("*/*")]
|
||||||
[InlineData("", "text/foo")]
|
public void FormatterMappings_Wildcardformat(string format)
|
||||||
public void FormatterMappings_SetFormatMapping_Invalid(string format, string contentType)
|
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
MediaTypeHeaderValue mediaType = null;
|
|
||||||
if (!string.IsNullOrEmpty(contentType))
|
|
||||||
{
|
|
||||||
mediaType = MediaTypeHeaderValue.Parse(contentType);
|
|
||||||
}
|
|
||||||
|
|
||||||
var options = new FormatterMappings();
|
var options = new FormatterMappings();
|
||||||
var expectedError = "Value cannot be null or empty." + Environment.NewLine + "Parameter name: format";
|
var expected = string.Format(@"The media type {0} is not valid. The media type containing ""<mediatype>/*"" are not supported.", format);
|
||||||
|
|
||||||
// Act and Assert
|
// Act and assert
|
||||||
Assert.Throws<ArgumentException>(() => options.SetMediaTypeMappingForFormat(format, mediaType));
|
var exception = Assert.Throws<ArgumentException>(() => options.SetMediaTypeMappingForFormat("star", MediaTypeHeaderValue.Parse(format)));
|
||||||
|
Assert.Equal(expected, exception.Message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -141,10 +141,11 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests
|
||||||
var client = server.CreateClient();
|
var client = server.CreateClient();
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var response = await client.GetAsync("http://localhost/ProducesDerived/ReturnClassName.json");
|
var response = await client.GetAsync("http://localhost/ProducesOverride/ReturnClassName");
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
|
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||||
|
Assert.Equal(@"ProducesOverrideController", await response.Content.ReadAsStringAsync());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -20,4 +20,9 @@
|
||||||
<SchemaVersion>2.0</SchemaVersion>
|
<SchemaVersion>2.0</SchemaVersion>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
<Import Project="$(VSToolsPath)\AspNet\Microsoft.Web.AspNet.targets" Condition="'$(VSToolsPath)' != ''" />
|
<Import Project="$(VSToolsPath)\AspNet\Microsoft.Web.AspNet.targets" Condition="'$(VSToolsPath)' != ''" />
|
||||||
|
<ProjectExtensions>
|
||||||
|
<VisualStudio>
|
||||||
|
<UserProperties project_1json__JSONSchema="http://www.asp.net/media/4878834/project.json" />
|
||||||
|
</VisualStudio>
|
||||||
|
</ProjectExtensions>
|
||||||
</Project>
|
</Project>
|
||||||
|
|
@ -17,6 +17,7 @@
|
||||||
"ErrorPageMiddlewareWebSite": "1.0.0",
|
"ErrorPageMiddlewareWebSite": "1.0.0",
|
||||||
"FilesWebSite": "1.0.0",
|
"FilesWebSite": "1.0.0",
|
||||||
"FiltersWebSite": "1.0.0",
|
"FiltersWebSite": "1.0.0",
|
||||||
|
"FormatFilterWebSite": "1.0.0-*",
|
||||||
"FormatterWebSite": "1.0.0",
|
"FormatterWebSite": "1.0.0",
|
||||||
"InlineConstraintsWebSite": "1.0.0",
|
"InlineConstraintsWebSite": "1.0.0",
|
||||||
"LoggingWebSite": "1.0.0",
|
"LoggingWebSite": "1.0.0",
|
||||||
|
|
@ -44,8 +45,7 @@
|
||||||
"MvcTagHelpersWebSite": "1.0.0",
|
"MvcTagHelpersWebSite": "1.0.0",
|
||||||
"Microsoft.Framework.ConfigurationModel.Json": "1.0.0-*",
|
"Microsoft.Framework.ConfigurationModel.Json": "1.0.0-*",
|
||||||
"xunit.runner.kre": "1.0.0-*",
|
"xunit.runner.kre": "1.0.0-*",
|
||||||
"Microsoft.AspNet.WebUtilities": "1.0.0-*",
|
"Microsoft.AspNet.WebUtilities": "1.0.0-*"
|
||||||
"FormatFilterWebSite": "1.0.0-*"
|
|
||||||
},
|
},
|
||||||
"commands": {
|
"commands": {
|
||||||
"test": "xunit.runner.kre"
|
"test": "xunit.runner.kre"
|
||||||
|
|
|
||||||
|
|
@ -15,9 +15,4 @@
|
||||||
<DevelopmentServerPort>49641</DevelopmentServerPort>
|
<DevelopmentServerPort>49641</DevelopmentServerPort>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
<Import Project="$(VSToolsPath)\AspNet\Microsoft.Web.AspNet.targets" Condition="'$(VSToolsPath)' != ''" />
|
<Import Project="$(VSToolsPath)\AspNet\Microsoft.Web.AspNet.targets" Condition="'$(VSToolsPath)' != ''" />
|
||||||
<ProjectExtensions>
|
|
||||||
<VisualStudio>
|
|
||||||
<UserProperties project_1json__JSONSchema="http://www.asp.net/media/4878834/project.json" />
|
|
||||||
</VisualStudio>
|
|
||||||
</ProjectExtensions>
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|
@ -5,7 +5,6 @@ using System.Security.Claims;
|
||||||
using Microsoft.AspNet.Builder;
|
using Microsoft.AspNet.Builder;
|
||||||
using Microsoft.AspNet.Mvc;
|
using Microsoft.AspNet.Mvc;
|
||||||
using Microsoft.AspNet.Security;
|
using Microsoft.AspNet.Security;
|
||||||
using Microsoft.AspNet.Routing;
|
|
||||||
using Microsoft.Framework.DependencyInjection;
|
using Microsoft.Framework.DependencyInjection;
|
||||||
|
|
||||||
namespace FiltersWebSite
|
namespace FiltersWebSite
|
||||||
|
|
|
||||||
|
|
@ -1,17 +0,0 @@
|
||||||
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
|
|
||||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
|
||||||
|
|
||||||
using Microsoft.AspNet.Mvc;
|
|
||||||
|
|
||||||
namespace FormatFilterWebSite
|
|
||||||
{
|
|
||||||
public class ProducesBaseController : Controller
|
|
||||||
{
|
|
||||||
[Produces("application/custom_ProducesBaseController_Action")]
|
|
||||||
public virtual string ReturnClassName()
|
|
||||||
{
|
|
||||||
// Should be written using the action's content type. Overriding the one at the class.
|
|
||||||
return "ProducesBaseController";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -5,11 +5,11 @@ using Microsoft.AspNet.Mvc;
|
||||||
|
|
||||||
namespace FormatFilterWebSite
|
namespace FormatFilterWebSite
|
||||||
{
|
{
|
||||||
[Produces("application/custom_ProducesOverrideController")]
|
[Produces("application/custom_ProducesController")]
|
||||||
public class ProducesOverrideController : ProducesBaseController
|
public class ProducesOverrideController
|
||||||
{
|
{
|
||||||
[FormatFilter]
|
[Produces("application/ProducesMethod")]
|
||||||
public override string ReturnClassName()
|
public string ReturnClassName()
|
||||||
{
|
{
|
||||||
// should be written using the content defined at base class's action.
|
// should be written using the content defined at base class's action.
|
||||||
return "ProducesOverrideController";
|
return "ProducesOverrideController";
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue