Adding functional tests and attribute route tests.

Incorporating PR coments.
This commit is contained in:
Mugdha Kulkarni 2015-01-15 21:35:17 -08:00
parent d91b7776b3
commit c8b911b596
35 changed files with 1124 additions and 335 deletions

15
Mvc.sln
View File

@ -130,6 +130,8 @@ Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "ValidationWebSite", "test\W
EndProject
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Microsoft.AspNet.Mvc.Xml.Test", "test\Microsoft.AspNet.Mvc.Xml.Test\Microsoft.AspNet.Mvc.Xml.Test.kproj", "{22019146-BDFA-442E-8C8E-345FB9644578}"
EndProject
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "FormatFilterWebSite", "test\WebSites\FormatFilterWebSite\FormatFilterWebSite.kproj", "{AC9BE567-540E-4C70-90C2-AAF021307A80}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@ -740,6 +742,18 @@ Global
{22019146-BDFA-442E-8C8E-345FB9644578}.Release|Mixed Platforms.Build.0 = Release|Any CPU
{22019146-BDFA-442E-8C8E-345FB9644578}.Release|x86.ActiveCfg = Release|Any CPU
{22019146-BDFA-442E-8C8E-345FB9644578}.Release|x86.Build.0 = Release|Any CPU
{AC9BE567-540E-4C70-90C2-AAF021307A80}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{AC9BE567-540E-4C70-90C2-AAF021307A80}.Debug|Any CPU.Build.0 = Debug|Any CPU
{AC9BE567-540E-4C70-90C2-AAF021307A80}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
{AC9BE567-540E-4C70-90C2-AAF021307A80}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
{AC9BE567-540E-4C70-90C2-AAF021307A80}.Debug|x86.ActiveCfg = Debug|Any CPU
{AC9BE567-540E-4C70-90C2-AAF021307A80}.Debug|x86.Build.0 = Debug|Any CPU
{AC9BE567-540E-4C70-90C2-AAF021307A80}.Release|Any CPU.ActiveCfg = Release|Any CPU
{AC9BE567-540E-4C70-90C2-AAF021307A80}.Release|Any CPU.Build.0 = Release|Any CPU
{AC9BE567-540E-4C70-90C2-AAF021307A80}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
{AC9BE567-540E-4C70-90C2-AAF021307A80}.Release|Mixed Platforms.Build.0 = Release|Any CPU
{AC9BE567-540E-4C70-90C2-AAF021307A80}.Release|x86.ActiveCfg = Release|Any CPU
{AC9BE567-540E-4C70-90C2-AAF021307A80}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@ -803,5 +817,6 @@ Global
{C3123A70-41C4-4122-AD1C-D35DF8958DD7} = {16703B76-C9F7-4C75-AE6C-53D92E308E3C}
{87AB84B2-22C1-43C6-BB8A-1D327B446FB0} = {16703B76-C9F7-4C75-AE6C-53D92E308E3C}
{22019146-BDFA-442E-8C8E-345FB9644578} = {3BA657BF-28B1-42DA-B5B0-1C4601FCF7B1}
{AC9BE567-540E-4C70-90C2-AAF021307A80} = {16703B76-C9F7-4C75-AE6C-53D92E308E3C}
EndGlobalSection
EndGlobal

View File

@ -0,0 +1,22 @@
// 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 MvcSample.Web.Controllers
{
[FormatFilter]
public class FormatFilterController : Controller
{
public Product GetProduct(int id)
{
return new Product() { SampleInt = id };
}
[Produces("application/custom", "application/json", "text/json")]
public Product ProducesMethod(int id)
{
return new Product() { SampleInt = id }; ;
}
}
}

View File

@ -0,0 +1,9 @@
using System;
namespace MvcSample.Web
{
public class Product
{
public int SampleInt { get; set; }
}
}

View File

@ -11,7 +11,6 @@ using Microsoft.Framework.ConfigurationModel;
using Microsoft.Framework.DependencyInjection;
using MvcSample.Web.Filters;
using MvcSample.Web.Services;
using Microsoft.AspNet.Mvc.Core.Filters;
#if ASPNET50
using Autofac;
@ -64,13 +63,9 @@ namespace MvcSample.Web
services.Configure<MvcOptions>(options =>
{
options.Filters.Add(typeof(PassThroughAttribute), order: 17);
<<<<<<< HEAD
options.AddXmlDataContractSerializerFormatter();
=======
var formatFilter = new FormatFilter();
var formatFilter = new FormatFilterAttribute();
options.Filters.Add(formatFilter);
>>>>>>> This is MVC part of feature URL Extensions. It does following:
});
services.Configure<RazorViewEngineOptions>(options =>
{
@ -113,7 +108,7 @@ namespace MvcSample.Web
options.Filters.Add(typeof(PassThroughAttribute), order: 17);
options.AddXmlDataContractSerializerFormatter();
var formatFilter = new FormatFilter();
var formatFilter = new FormatFilterAttribute();
options.Filters.Add(formatFilter);
});
});
@ -121,10 +116,8 @@ namespace MvcSample.Web
app.UseMvc(routes =>
{
routes.MapRoute("FormatRoute", "{controller}/{action}/{id}.{format?}");
routes.MapRoute("areaRoute", "{area:exists}/{controller}/{action}");
routes.MapRoute("formatRoute", "{controller}/{action}/{format}");
routes.MapRoute(
"controllerActionRoute",
"{controller}/{action}",

View File

@ -1,90 +0,0 @@
using System;
using Microsoft.Framework.OptionsModel;
using System.Threading.Tasks;
using System.Linq;
namespace Microsoft.AspNet.Mvc.Core.Filters
{
public class FormatFilter : IFormatFilter
{
public void OnResourceExecuting([NotNull] ResourceExecutingContext context)
{
var options = (IOptions<MvcOptions>)context.HttpContext.RequestServices.GetService(
typeof(IOptions<MvcOptions>));
string format = null;
if (context.RouteData.Values.ContainsKey("format"))
{
format = context.RouteData.Values["format"].ToString();
}
else if(context.HttpContext.Request.Query.ContainsKey("format"))
{
format = context.HttpContext.Request.Query.Get("format").ToString();
}
if (format != null)
{
var contentType = options.Options.OutputFormatterOptions.GetContentTypeForFormat(format);
if (contentType == null)
{
// no contentType exists for the foramt, return 404
context.Result = new HttpNotFoundResult();
}
else
{
if (context.Filters.Any(f => f is ProducesAttribute))
{
var produces = context.Filters.First(f => f is ProducesAttribute) as ProducesAttribute;
if(!produces.ContentTypes.Contains(contentType))
{
context.Result = new HttpNotFoundResult();
}
}
}
}
}
public void OnResourceExecuted([NotNull] ResourceExecutedContext context)
{
}
public void OnResultExecuting([NotNull]ResultExecutingContext context)
{
var options = (IOptions<MvcOptions>)context.HttpContext.RequestServices.GetService(
typeof(IOptions<MvcOptions>));
string format = null;
if (context.RouteData.Values.ContainsKey("format"))
{
format = context.RouteData.Values["format"].ToString();
}
else if (context.HttpContext.Request.Query.ContainsKey("format"))
{
format = context.HttpContext.Request.Query.Get("format").ToString();
}
if (format != null)
{
var contentType = options.Options.OutputFormatterOptions.GetContentTypeForFormat(format);
if (contentType != null)
{
var objectResult = context.Result as ObjectResult;
if (objectResult != null)
{
objectResult.ContentTypes.Clear();
objectResult.ContentTypes.Add(contentType);
}
}
else
{
context.Result = new HttpStatusCodeResult(404);
}
}
}
public void OnResultExecuted([NotNull]ResultExecutedContext context)
{
}
}
}

View File

@ -0,0 +1,122 @@
// 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 System;
using System.Diagnostics;
using System.Linq;
using Microsoft.AspNet.Mvc.Description;
using Microsoft.Framework.DependencyInjection;
using Microsoft.Framework.OptionsModel;
using Microsoft.Net.Http.Headers;
using System.Collections.Generic;
namespace Microsoft.AspNet.Mvc
{
/// <summary>
/// This will look at the format parameter if present in the route data or query data and
/// sets the content type in ObjectResult corresponding to the format value.
/// </summary>
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = true)]
public class FormatFilterAttribute : Attribute, IFormatFilter, IResourceFilter, IResultFilter
{
/// <summary>
/// As a resourceFilter, this filter looks at the request and rejects it
/// before going ahead if
/// 1. The Format in the request doesnt match any format in the map.
/// 2. If there is a conflicting producesFilter.
/// </summary>
/// <param name="context"></param>
public void OnResourceExecuting([NotNull] ResourceExecutingContext context)
{
var format = GetFormat(context);
if (format != null && !string.IsNullOrEmpty(format.ToString()))
{
var formatContentType = GetContentType(format, context);
if (formatContentType == null)
{
// no contentType exists for the format, return 404
context.Result = new HttpNotFoundResult();
}
else
{
var responseTypeFilters = context.Filters.OfType<IApiResponseMetadataProvider>();
if (responseTypeFilters.Count() != 0)
{
var contentTypes = new List<MediaTypeHeaderValue>();
foreach (var filter in responseTypeFilters)
{
filter.SetContentTypes(contentTypes);
}
if (!contentTypes.Any(c => c.IsSubsetOf(formatContentType)))
{
context.Result = new HttpNotFoundResult();
}
}
}
}
}
public void OnResourceExecuted([NotNull] ResourceExecutedContext context)
{
}
public void OnResultExecuting([NotNull] ResultExecutingContext context)
{
var format = GetFormat(context);
if (format != null)
{
var contentType = GetContentType(format, context);
Debug.Assert(contentType != null);
var objectResult = context.Result as ObjectResult;
if (objectResult != null)
{
objectResult.ContentTypes.Clear();
objectResult.ContentTypes.Add(contentType);
}
}
}
public void OnResultExecuted([NotNull] ResultExecutedContext context)
{
}
public MediaTypeHeaderValue GetContentTypeForCurrentRequest(FilterContext context)
{
var format = GetFormat(context);
if (format != null && !string.IsNullOrEmpty(format.ToString()))
{
return GetContentType(format, context);
}
return null;
}
private object GetFormat(FilterContext context)
{
object format = null;
if (!context.RouteData.Values.TryGetValue("format", out format))
{
if (context.HttpContext.Request.Query.ContainsKey("format"))
{
format = context.HttpContext.Request.Query.Get("format");
}
}
return format;
}
private MediaTypeHeaderValue GetContentType(object format, FilterContext context)
{
Debug.Assert(format != null);
var options = context.HttpContext.RequestServices.GetService<IOptions<MvcOptions>>();
var contentType = options.Options.FormatterMappings.GetContentTypeForFormat(format.ToString());
return contentType;
}
}
}

View File

@ -1,9 +1,16 @@
using System;
// 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.
namespace Microsoft.AspNet.Mvc.Core.Filters
using Microsoft.Net.Http.Headers;
using System;
namespace Microsoft.AspNet.Mvc
{
public interface IFormatFilter : IResourceFilter, IResultFilter
/// <summary>
/// Implement this interface if you want to have your own implementation of FormatFilter
/// </summary>
public interface IFormatFilter : IFilter
{
MediaTypeHeaderValue GetContentTypeForCurrentRequest(FilterContext context);
}
}

View File

@ -9,7 +9,6 @@ using Microsoft.AspNet.Mvc.Core;
using Microsoft.AspNet.Mvc.Description;
using Microsoft.Net.Http.Headers;
namespace Microsoft.AspNet.Mvc
{
/// <summary>
@ -37,7 +36,8 @@ namespace Microsoft.AspNet.Mvc
{
// Check if FormatFilter has already set the content type
// If it has, dont override it
if (objectResult.ContentTypes.Count == 0)
var formatFilter = context.Filters.OfType<IFormatFilter>().LastOrDefault();
if (formatFilter == null || formatFilter.GetContentTypeForCurrentRequest(context) == null)
{
SetContentTypes(objectResult.ContentTypes);
}

View File

@ -0,0 +1,54 @@
// 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 System;
using System.Collections.Generic;
using Microsoft.Net.Http.Headers;
using System.Collections.ObjectModel;
using System.Globalization;
using Microsoft.AspNet.Mvc.Core;
namespace Microsoft.AspNet.Mvc
{
/// <summary>
/// These options are used to specify mapping between the Url Format and corresponding ContentType.
/// </summary>
public class FormatterMappings
{
private readonly Dictionary<string, MediaTypeHeaderValue> _map =
new Dictionary<string, MediaTypeHeaderValue>(StringComparer.OrdinalIgnoreCase);
public void SetFormatMapping([NotNull] string format, [NotNull] MediaTypeHeaderValue contentType)
{
if (contentType == null)
{
throw new ArgumentException((Resources.ArgumentCannotBeNullOrEmpty), "contentType");
}
format = RemovePeriodIfPresent(format);
_map[format] = contentType;
}
public MediaTypeHeaderValue GetContentTypeForFormat(string format)
{
format = RemovePeriodIfPresent(format);
MediaTypeHeaderValue value = null;
_map.TryGetValue(format, out value);
return value;
}
private string RemovePeriodIfPresent(string format)
{
if (string.IsNullOrEmpty(format))
{
throw new ArgumentException(Resources.ArgumentCannotBeNullOrEmpty, "format");
}
if (format.StartsWith("."))
{
format = format.Substring(1);
}
return format;
}
}
}

View File

@ -18,7 +18,6 @@ namespace Microsoft.AspNet.Mvc
{
private AntiForgeryOptions _antiForgeryOptions = new AntiForgeryOptions();
private int _maxModelStateErrors = ModelStateDictionary.DefaultMaxAllowedErrors;
//private OutputFormatterOptions _outputFormatterOptions = new OutputFormatterOptions();
public MvcOptions()
{
@ -29,7 +28,7 @@ namespace Microsoft.AspNet.Mvc
OutputFormatters = new List<OutputFormatterDescriptor>();
InputFormatters = new List<InputFormatterDescriptor>();
Filters = new List<IFilter>();
OutputFormatterOptions = new OutputFormatterOptions();
FormatterMappings = new FormatterMappings();
}
/// <summary>
@ -55,7 +54,7 @@ namespace Microsoft.AspNet.Mvc
}
}
public OutputFormatterOptions OutputFormatterOptions { get; }
public FormatterMappings FormatterMappings { get; }
/// <summary>
/// Gets a list of <see cref="IFilter"/> which are used to construct filters that
@ -69,16 +68,6 @@ namespace Microsoft.AspNet.Mvc
/// </summary>
public List<OutputFormatterDescriptor> OutputFormatters { get; private set; }
/// <summary>
/// Sets the mapping for output format specified in URL (extension) and content type
/// </summary>
/// <param name="format">URL extension for output format</param>
/// <param name="contentType">Content type mapping to the format</param>
public void AddFormatMapping(string format, MediaTypeHeaderValue contentType)
{
OutputFormatterOptions.AddFormatMapping(format, contentType);
}
/// <summary>
/// Gets a list of the <see cref="InputFormatterDescriptor" /> which are used to construct
/// a list of <see cref="IInputFormatter"/> by <see cref="IInputFormattersProvider"/>.

View File

@ -1,46 +0,0 @@
using System;
using System.Collections.Generic;
using Microsoft.Net.Http.Headers;
namespace Microsoft.AspNet.Mvc.Core
{
public class OutputFormatterOptions
{
private Dictionary<string, MediaTypeHeaderValue> map = new Dictionary<string, MediaTypeHeaderValue>();
public void AddFormatMapping(string format, MediaTypeHeaderValue contentType)
{
if (!string.IsNullOrEmpty(format) && contentType != null)
{
if(format.StartsWith("."))
{
format = format.TrimStart('.');
}
map[format.ToLower()] = contentType;
}
}
public MediaTypeHeaderValue GetContentTypeForFormat(string format)
{
if (!string.IsNullOrEmpty(format))
{
if (format.StartsWith("."))
{
format = format.TrimStart('.');
}
if (map.ContainsKey(format.ToLower()))
{
return map[format.ToLower()];
}
else
{
return null;
}
}
return null;
}
}
}

View File

@ -13,9 +13,7 @@
"Microsoft.AspNet.Routing": "1.0.0-*",
"Microsoft.AspNet.Security": "1.0.0-*",
"Microsoft.AspNet.Security.DataProtection": "1.0.0-*",
"Microsoft.Framework.Runtime.Interfaces": { "version": "1.0.0-*", "type": "build" },
"Microsoft.Net.Http": "2.2.13.0",
"Microsoft.Net.Http.Server": "1.0.0.0-rc1-11332"
"Microsoft.Framework.Runtime.Interfaces": { "version": "1.0.0-*", "type": "build" }
},
"frameworks": {
"aspnet50": {},

View File

@ -46,9 +46,8 @@ namespace Microsoft.AspNet.Mvc
options.OutputFormatters.Add(new StringOutputFormatter());
options.OutputFormatters.Add(new JsonOutputFormatter());
// Set up default mapping for xml and json extensions to content type
options.AddFormatMapping("json", MediaTypeHeaderValue.Parse("application/json"));
options.AddFormatMapping("xml", MediaTypeHeaderValue.Parse("application/xml"));
// Set up default mapping for json extensions to content type
options.FormatterMappings.SetFormatMapping("json", MediaTypeHeaderValue.Parse("application/json"));
// Set up default input formatters.
options.InputFormatters.Add(new JsonInputFormatter());

View File

@ -1,223 +1,348 @@
using System;
using System.Threading.Tasks;
using Microsoft.AspNet.Mvc.Core.Filters;
using Microsoft.AspNet.PipelineCore;
// 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 System;
using Microsoft.AspNet.Http;
using Microsoft.AspNet.Mvc;
using Microsoft.AspNet.Http.Core;
using Microsoft.AspNet.Routing;
using Microsoft.Framework.DependencyInjection;
using Microsoft.Framework.OptionsModel;
using Microsoft.Net.Http.Headers;
using Xunit;
#if ASPNET50
using Moq;
using Microsoft.Framework.OptionsModel;
using Microsoft.AspNet.Http;
using System.Net;
#endif
namespace Microsoft.AspNet.Mvc.Core.Test
namespace Microsoft.AspNet.Mvc
{
public enum FormatPlace
{
RouteData,
QueryData,
RouteAndQueryData
}
public class FormatFilterTests
{
public enum FormatSource
{
RouteData,
QueryData,
RouteAndQueryData
}
#if ASPNET50
[Theory]
[InlineData("json", FormatPlace.RouteData, "application/json")]
[InlineData("json", FormatPlace.QueryData, "application/json")]
[InlineData("json", FormatPlace.RouteAndQueryData, "application/json")]
public void FormatFilter_ContextContainsFormat_DefaultFormat(string format,
FormatPlace place,
[InlineData("json", FormatSource.RouteData, "application/json")]
[InlineData("json", FormatSource.QueryData, "application/json")]
[InlineData("json", FormatSource.RouteAndQueryData, "application/json")]
public void FormatFilter_ContextContainsFormat_DefaultFormat(
string format,
FormatSource place,
string contentType)
{
// Arrange
var mediaType = MediaTypeHeaderValue.Parse(contentType);
var context = CreateResultExecutingContext(format, place);
var filter = new FormatFilter();
var mediaType = MediaTypeHeaderValue.Parse("application/json");
var resultExecutingContext = CreateResultExecutingContext(
format,
FormatSource.RouteData);
var resourceExecutingContext = CreateResourceExecutingContext(
new IFilter[] { },
format,
FormatSource.RouteData);
var filter = new FormatFilterAttribute();
// Act
filter.OnResultExecuting(context);
filter.OnResourceExecuting(resourceExecutingContext);
// Assert
Assert.Null(resourceExecutingContext.Result);
// Act
filter.OnResultExecuting(resultExecutingContext);
// Assert
var objectResult = context.Result as ObjectResult;
var objectResult = Assert.IsType<ObjectResult>(resultExecutingContext.Result);
Assert.Equal(1, objectResult.ContentTypes.Count);
ValidateMediaType(mediaType, objectResult.ContentTypes[0]);
AssertMediaTypesEqual(mediaType, objectResult.ContentTypes[0]);
}
[Fact]
public void FormatFilter_ContextContainsFormat_InRouteAndQueryData()
{
// Arrange
var mediaType = MediaTypeHeaderValue.Parse("application/json");
var httpContext = CreateMockHttpContext();
// Routedata contains json
var data = new RouteData();
data.Values.Add("format", "json");
// Query contains xml
httpContext.Setup(c => c.Request.Query.ContainsKey("format")).Returns(true);
httpContext.Setup(c => c.Request.Query.Get("format")).Returns("xml");
var ac = new ActionContext(httpContext.Object, data, new ActionDescriptor());
var resultExecutingContext = new ResultExecutingContext(
ac,
new IFilter[] { },
new ObjectResult("Hello!"),
controller: new object());
var resourceExecutingContext = new ResourceExecutingContext(
ac,
new IFilter[] { });
var filter = new FormatFilterAttribute();
// Act
filter.OnResourceExecuting(resourceExecutingContext);
filter.OnResultExecuting(resultExecutingContext);
// Assert
var objectResult = Assert.IsType<ObjectResult>(resultExecutingContext.Result);
Assert.Equal(1, objectResult.ContentTypes.Count);
AssertMediaTypesEqual(mediaType, objectResult.ContentTypes[0]);
}
[Theory]
[InlineData("foo", FormatPlace.RouteData, "application/foo")]
[InlineData("foo", FormatPlace.QueryData, "application/foo")]
[InlineData("foo", FormatPlace.RouteAndQueryData, "application/foo")]
[InlineData("foo", FormatSource.RouteData, "application/foo")]
[InlineData("foo", FormatSource.QueryData, "application/foo")]
[InlineData("foo", FormatSource.RouteAndQueryData, "application/foo")]
public void FormatFilter_ContextContainsFormat_Custom(
string format,
FormatPlace place,
FormatSource place,
string contentType)
{
// Arrange
var mediaType = MediaTypeHeaderValue.Parse(contentType);
var context = CreateResultExecutingContext(format, place);
var options = (IOptions<MvcOptions>)context.HttpContext.RequestServices.GetService(
typeof(IOptions<MvcOptions>));
options.Options.AddFormatMapping(format, MediaTypeHeaderValue.Parse(contentType));
var filter = new FormatFilter();
var resultExecutingContext = CreateResultExecutingContext(format, place);
var resourceExecutingContext = CreateResourceExecutingContext(new IFilter[] { }, format, place);
var options = resultExecutingContext.HttpContext.RequestServices.GetService<IOptions<MvcOptions>>();
options.Options.FormatterMappings.SetFormatMapping(format, MediaTypeHeaderValue.Parse(contentType));
var filter = new FormatFilterAttribute();
// Act
filter.OnResultExecuting(context);
filter.OnResourceExecuting(resourceExecutingContext);
filter.OnResultExecuting(resultExecutingContext);
// Assert
var objectResult = context.Result as ObjectResult;
var objectResult = Assert.IsType<ObjectResult>(resultExecutingContext.Result);
Assert.Equal(1, objectResult.ContentTypes.Count);
ValidateMediaType(mediaType, objectResult.ContentTypes[0]);
AssertMediaTypesEqual(mediaType, objectResult.ContentTypes[0]);
}
[Theory]
[InlineData("foo", FormatPlace.RouteData, "application/foo")]
public void FormatFilter_ContextContainsFormat_NonExisting(
[InlineData("foo", FormatSource.RouteData, "application/foo")]
[InlineData("foo", FormatSource.QueryData, "application/foo")]
public void FormatFilter_ContextContainsNonExistingFormat(
string format,
FormatPlace place,
FormatSource place,
string contentType)
{
// Arrange
var mediaType = MediaTypeHeaderValue.Parse(contentType);
var resourceExecutingContext = CreateResourceExecutingContext(new IFilter[] { }, format, place);
var filter = new FormatFilter();
var filter = new FormatFilterAttribute();
// Act
filter.OnResourceExecuting(resourceExecutingContext);
// Assert
var actionResult = resourceExecutingContext.Result;
Assert.True(actionResult is HttpNotFoundResult);
Assert.IsType<HttpNotFoundResult>(actionResult);
}
[Fact]
public void FormatFilter_ContextDoesntContainFormat()
{
// Arrange
// Arrange
var resourceExecutingContext = CreateResourceExecutingContext(new IFilter[] { });
var filter = new FormatFilter();
var filter = new FormatFilterAttribute();
// Act
filter.OnResourceExecuting(resourceExecutingContext);
// Assert
var result = resourceExecutingContext.Result as IActionResult;
Assert.False(result is HttpNotFoundResult);
Assert.Null(resourceExecutingContext.Result);
}
[Theory]
[InlineData("json", FormatPlace.RouteData, "application/json")]
[InlineData("json", FormatPlace.QueryData, "application/json")]
[InlineData("json", FormatSource.RouteData, "application/json")]
[InlineData("json", FormatSource.QueryData, "application/json")]
public void FormatFilter_ContextContainsFormat_ContainsProducesFilter_Matching(
string format,
FormatPlace place,
FormatSource place,
string contentType)
{
// Arrange
var produces = new ProducesAttribute(contentType, new string[] { "application/foo", "text/bar" });
var context = CreateResourceExecutingContext(new IFilter[] { produces }, format, place);
var filter = new FormatFilter();
var filter = new FormatFilterAttribute();
// Act
filter.OnResourceExecuting(context);
// Assert
var result = context.Result as IActionResult;
Assert.False(result is HttpNotFoundResult);
// Assert
Assert.Null(context.Result);
}
[Fact]
public void FormatFilter_ContextContainsFormat_ContainsProducesFilter_WildCardMatching()
{
// Arrange
var produces = new ProducesAttribute(
"application/baz",
new string[] { "application/foo", "text/bar" });
var context = CreateResourceExecutingContext(new IFilter[] { produces }, "star", FormatSource.RouteData);
var options = context.HttpContext.RequestServices.GetService<IOptions<MvcOptions>>();
options.Options.FormatterMappings.SetFormatMapping("star", MediaTypeHeaderValue.Parse("application/*"));
var filter = new FormatFilterAttribute();
// Act
filter.OnResourceExecuting(context);
// Assert
Assert.Null(context.Result);
}
[Theory]
[InlineData("json", FormatPlace.RouteData, "application/json")]
[InlineData("json", FormatPlace.QueryData, "application/json")]
[InlineData("json", FormatSource.RouteData, "application/json")]
[InlineData("json", FormatSource.QueryData, "application/json")]
public void FormatFilter_ContextContainsFormat_ContainsProducesFilter_Conflicting(
string format,
FormatPlace place,
FormatSource place,
string contentType)
{
// Arrange
var mediaType = MediaTypeHeaderValue.Parse(contentType);
var produces = new ProducesAttribute("application/xml", new string[] { "application/foo", "text/bar" });
var context = CreateResourceExecutingContext(new IFilter[] { produces }, format, place);
var filter = new FormatFilter();
var filter = new FormatFilterAttribute();
// Act
filter.OnResourceExecuting(context);
// Assert
var result = context.Result as IActionResult;
Assert.True(result is HttpNotFoundResult);
var result = Assert.IsType<HttpNotFoundResult>(context.Result);
}
[Theory]
[InlineData("", FormatSource.RouteData)]
[InlineData(null, FormatSource.QueryData)]
[InlineData("", FormatSource.RouteData)]
[InlineData(null, FormatSource.QueryData)]
public void FormatFilter_ContextContainsFormat_Invalid(
string format,
FormatSource place)
{
// Arrange
var resourceExecutingContext = CreateResourceExecutingContext(
new IFilter[] { },
format,
FormatSource.RouteData);
var filter = new FormatFilterAttribute();
// Act
filter.OnResourceExecuting(resourceExecutingContext);
// Assert
Assert.Null(resourceExecutingContext.Result);
}
[Theory]
[InlineData("json", FormatSource.RouteData, "application/json")]
[InlineData("json", FormatSource.QueryData, "application/json")]
[InlineData("", FormatSource.RouteAndQueryData, null)]
[InlineData(null, FormatSource.RouteAndQueryData, null)]
public void FormatFilter_GetContentTypeForRequest(
string format,
FormatSource place,
string contentType)
{
// Arrange
var resourceExecutingContext = CreateResourceExecutingContext(
new IFilter[] { },
format,
FormatSource.RouteData);
var filter = new FormatFilterAttribute();
var returnContentType = filter.GetContentTypeForCurrentRequest(resourceExecutingContext);
MediaTypeHeaderValue mediaType = null;
if (returnContentType != null)
{
mediaType = MediaTypeHeaderValue.Parse("application/json");
}
Assert.Equal(mediaType, returnContentType);
}
private static ResourceExecutingContext CreateResourceExecutingContext(
IFilter[] filters,
string format = null,
FormatPlace? place = null)
FormatSource? place = null)
{
if(format == null || place == null)
if (format == null || place == null)
{
var context = new ResourceExecutingContext(
CreateActionContext(),
filters);
context.Result = new HttpStatusCodeResult(200);
return context;
}
var context1 = new ResourceExecutingContext(
CreateActionContext(format, place),
filters);
context1.Result = new HttpStatusCodeResult(200);
return context1;
}
private static ResultExecutingContext CreateResultExecutingContext(
string format = null,
FormatPlace? place = null)
FormatSource? place = null)
{
if (format == null || place == null)
if (format == null && place == null)
{
return new ResultExecutingContext(
new ActionContext(new DefaultHttpContext(), new RouteData(), new ActionDescriptor()),
new IFilter[] { },
new ObjectResult("Some Value"));
new ObjectResult("Some Value"),
controller: new object());
}
return new ResultExecutingContext(
CreateActionContext(format, place),
new IFilter[] { },
new ObjectResult("Some Value"));
new ObjectResult("Some Value"),
controller: new object());
}
private static ActionContext CreateActionContext(string format = null, FormatPlace? place = null)
private static ActionContext CreateActionContext(string format = null, FormatSource? place = null)
{
var httpContext = CreateMockHttpContext();
var data = new RouteData();
if (place == FormatPlace.RouteData || place == FormatPlace.RouteAndQueryData)
{
var data = new RouteData();
if (place == FormatSource.RouteData || place == FormatSource.RouteAndQueryData)
{
data.Values.Add("format", format);
httpContext.Setup(c => c.Request.Query.ContainsKey("format")).Returns(false);
return new ActionContext(httpContext.Object, data, new ActionDescriptor());
httpContext.Setup(c => c.Request.Query.ContainsKey("format")).Returns(false);
}
if (place == FormatPlace.QueryData || place == FormatPlace.RouteAndQueryData)
if (place == FormatSource.QueryData || place == FormatSource.RouteAndQueryData)
{
httpContext.Setup(c => c.Request.Query.ContainsKey("format")).Returns(true);
httpContext.Setup(c => c.Request.Query.Get("format")).Returns(format);
return new ActionContext(httpContext.Object, new RouteData(), new ActionDescriptor());
httpContext.Setup(c => c.Request.Query.Get("format")).Returns(format);
}
else if(place == null && format == null)
else if (place == null && format == null)
{
httpContext.Setup(c => c.Request.Query.ContainsKey("format")).Returns(false);
return new ActionContext(httpContext.Object, new RouteData(), new ActionDescriptor());
httpContext.Setup(c => c.Request.Query.ContainsKey("format")).Returns(false);
}
return null;
return new ActionContext(httpContext.Object, data, new ActionDescriptor());
}
private static Mock<HttpContext> CreateMockHttpContext()
{
MvcOptions options = new MvcOptions();
var options = new MvcOptions();
MvcOptionsSetup.ConfigureMvc(options);
var mvcOptions = new Mock<IOptions<MvcOptions>>();
mvcOptions.Setup(o => o.Options).Returns(options);
@ -236,7 +361,9 @@ namespace Microsoft.AspNet.Mvc.Core.Test
return httpContext;
}
private static void ValidateMediaType(MediaTypeHeaderValue expectedMediaType, MediaTypeHeaderValue actualMediaType)
private static void AssertMediaTypesEqual(
MediaTypeHeaderValue expectedMediaType,
MediaTypeHeaderValue actualMediaType)
{
Assert.Equal(expectedMediaType.MediaType, actualMediaType.MediaType);
Assert.Equal(expectedMediaType.SubType, actualMediaType.SubType);
@ -249,5 +376,6 @@ namespace Microsoft.AspNet.Mvc.Core.Test
Assert.Equal(item.Value, NameValueHeaderValue.Find(actualMediaType.Parameters, item.Name).Value);
}
}
#endif
}
}

View File

@ -9,6 +9,10 @@ using Microsoft.AspNet.Routing;
using Microsoft.Net.Http.Headers;
using Xunit;
#if ASPNET50
using Moq;
#endif
namespace Microsoft.AspNet.Mvc.Test
{
public class ProducesAttributeTests
@ -20,7 +24,7 @@ namespace Microsoft.AspNet.Mvc.Test
var mediaType1 = MediaTypeHeaderValue.Parse("application/json");
var mediaType2 = MediaTypeHeaderValue.Parse("text/json;charset=utf-8");
var producesContentAttribute = new ProducesAttribute("application/json", "text/json;charset=utf-8");
var resultExecutingContext = CreateResultExecutingContext(producesContentAttribute);
var resultExecutingContext = CreateResultExecutingContext(new IFilter[] { producesContentAttribute });
var next = new ResultExecutionDelegate(
() => Task.FromResult(CreateResultExecutedContext(resultExecutingContext)));
@ -33,6 +37,33 @@ namespace Microsoft.AspNet.Mvc.Test
ValidateMediaType(mediaType1, objectResult.ContentTypes[0]);
ValidateMediaType(mediaType2, objectResult.ContentTypes[1]);
}
[Fact]
public async Task ProducesContentAttribute_FormatFilterAttribute()
{
// Arrange
var mediaType1 = MediaTypeHeaderValue.Parse("application/xml");
var mediaType2 = MediaTypeHeaderValue.Parse("application/json");
var producesContentAttribute = new ProducesAttribute("application/xml");
var formatFilter = new Mock<IFormatFilter>();
formatFilter.Setup(f => f.GetContentTypeForCurrentRequest(It.IsAny<FilterContext>()))
.Returns(mediaType2);
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(0, objectResult.ContentTypes.Count);
}
[Theory]
[InlineData("", "")]
@ -95,13 +126,12 @@ namespace Microsoft.AspNet.Mvc.Test
return new ResultExecutedContext(context, context.Filters, context.Result, context.Controller);
}
private static ResultExecutingContext CreateResultExecutingContext(IFilter filter)
private static ResultExecutingContext CreateResultExecutingContext(IFilter[] filters)
{
return new ResultExecutingContext(
CreateActionContext(),
new IFilter[] { filter, },
new ObjectResult("Some Value"),
controller: new object());
filters,
new ObjectResult("Some Value"));
}
private static ActionContext CreateActionContext()

View File

@ -0,0 +1,53 @@
// 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 System;
using Microsoft.Net.Http.Headers;
using Xunit;
namespace Microsoft.AspNet.Mvc
{
public class FormatterMappingsTest
{
[Theory]
[InlineData(".xml", "application/xml", "xml")]
[InlineData("json", "application/json", "JSON")]
[InlineData(".foo", "text/foo", "Foo")]
[InlineData(".Json", "application/json", "json")]
[InlineData("FOo", "text/foo", "FOO")]
public void FormatterMappings_SetFormatMapping_DiffSetGetFormat(string setFormat, string contentType, string getFormat)
{
// Arrange
var mediaType = MediaTypeHeaderValue.Parse(contentType);
var options = new FormatterMappings();
options.SetFormatMapping(setFormat, mediaType);
// Act
var returnmediaType = options.GetContentTypeForFormat(getFormat);
// Assert
Assert.Equal(mediaType, returnmediaType);
}
[Theory]
[InlineData("xml", null)]
[InlineData(".json", null)]
[InlineData(null, "application/json")]
[InlineData("", "text/foo")]
public void FormatterMappings_SetFormatMapping_Invalid(string format, string contentType)
{
// Arrange
MediaTypeHeaderValue mediaType = null;
if (!string.IsNullOrEmpty(contentType))
{
mediaType = MediaTypeHeaderValue.Parse(contentType);
}
var options = new FormatterMappings();
// Act and Assert
Assert.Throws<ArgumentException>(() => options.SetFormatMapping(format, mediaType));
}
}
}

View File

@ -1,72 +0,0 @@
using System;
using Microsoft.AspNet.Mvc.Core;
using Microsoft.Net.Http.Headers;
using Xunit;
namespace Microsoft.AspNet.Mvc.Core.Test
{
public class OutputFormatterOptionsTest
{
[Theory]
[InlineData("xml", "application/xml")]
[InlineData("json", "application/json")]
[InlineData("foo", "text/foo")]
[InlineData(".json", "application/json")]
[InlineData(".foo", "text/foo")]
public void OutputFormatterOptions_AddFormatMapping_Valid(string format, string contentType)
{
// Arrange
var mediaType = MediaTypeHeaderValue.Parse(contentType);
OutputFormatterOptions options = new OutputFormatterOptions();
options.AddFormatMapping(format, mediaType);
// Act
var returnmediaType = options.GetContentTypeForFormat(format);
// Assert
Assert.Equal(mediaType, returnmediaType);
}
[Theory]
[InlineData(".xml", "application/xml", "xml")]
[InlineData("json", "application/json", "JSON")]
[InlineData(".foo", "text/foo", "Foo")]
[InlineData(".Json", "application/json", "json")]
[InlineData("FOo", "text/foo", "FOO")]
public void OutputFormatterOptions_AddFormatMapping_DiffSetGetFormat(string setFormat, string contentType, string getFormat)
{
// Arrange
var mediaType = MediaTypeHeaderValue.Parse(contentType);
OutputFormatterOptions options = new OutputFormatterOptions();
options.AddFormatMapping(setFormat, mediaType);
// Act
var returnmediaType = options.GetContentTypeForFormat(getFormat);
// Assert
Assert.Equal(mediaType, returnmediaType);
}
[Theory]
[InlineData("xml", null)]
[InlineData(".json", null)]
[InlineData(null, "application/json")]
[InlineData("", "text/foo")]
public void OutputFormatterOptions_AddFormatMapping_Invalid(string format, string contentType)
{
// Arrange
MediaTypeHeaderValue mediaType = null;
if (!string.IsNullOrEmpty(contentType))
{
mediaType = MediaTypeHeaderValue.Parse(contentType);
}
OutputFormatterOptions options = new OutputFormatterOptions();
options.AddFormatMapping(format, mediaType);
// Act and Assert
Assert.Throws<ArgumentException>(() => options.GetContentTypeForFormat(format));
}
}
}

View File

@ -245,6 +245,119 @@ namespace Microsoft.AspNet.Mvc.Routing
}
}
[Theory]
[InlineData("moo/{p1}.{p2?}", "/moo/foo.bar", "foo", "bar", null)]
[InlineData("moo/{p1?}", "/moo/foo", "foo", null, null)]
[InlineData("moo/{p1?}", "/moo", null, null, null)]
[InlineData("moo/{p1}.{p2?}", "/moo/foo", "foo", null, null)]
[InlineData("moo/{p1}.{p2?}", "/moo/foo..bar", "foo.", "bar", null)]
[InlineData("moo/{p1}.{p2?}", "/moo/foo.moo.bar", "foo.moo", "bar", null)]
[InlineData("moo/{p1}.{p2}", "/moo/foo.bar", "foo", "bar", null)]
[InlineData("moo/foo.{p1}.{p2?}", "/moo/foo.moo.bar", "moo", "bar", null)]
[InlineData("moo/foo.{p1}.{p2?}", "/moo/foo.moo", "moo", null, null)]
[InlineData("moo/.{p2?}", "/moo/.foo", null, "foo", null)]
[InlineData("moo/{p1}.{p2?}", "/moo/....", "..", ".", null)]
[InlineData("moo/{p1}.{p2?}", "/moo/.bar", ".bar", null, null)]
[InlineData("moo/{p1}.{p2}.{p3?}", "/moo/foo.moo.bar", "foo", "moo", "bar")]
[InlineData("moo/{p1}.{p2}.{p3?}", "/moo/foo.moo", "foo", "moo", null)]
[InlineData("moo/{p1}.{p2}.{p3}.{p4?}", "/moo/foo.moo.bar", "foo", "moo", "bar")]
[InlineData("{p1}.{p2?}/{p3}", "/foo.moo/bar", "foo", "moo", "bar")]
[InlineData("{p1}.{p2?}/{p3}", "/foo/bar", "foo", null, "bar")]
[InlineData("{p1}.{p2?}/{p3}", "/.foo/bar", ".foo", null, "bar")]
public async Task AttributeRoute_WithOptionalCompositeParameter_Valid(
string template,
string request,
string p1,
string p2,
string p3)
{
// Arrange
var expectedRouteGroup = string.Format("{0}&&{1}", 0, template);
// We need to force the creation of a closure in order to avoid an issue with Moq and Roslyn.
var numberOfCalls = 0;
Action<RouteContext> callBack = ctx => { ctx.IsHandled = true; numberOfCalls++; };
var next = new Mock<IRouter>();
next.Setup(r => r.RouteAsync(It.IsAny<RouteContext>()))
.Callback(callBack)
.Returns(Task.FromResult(true))
.Verifiable();
var firstRoute = CreateMatchingEntry(next.Object, template, order: 0);
// We setup the route entries in reverse order of precedence to ensure that when we
// try to route the request, the route with a higher precedence gets tried first.
var matchingRoutes = new[] { firstRoute };
var linkGenerationEntries = Enumerable.Empty<AttributeRouteLinkGenerationEntry>();
var route = new AttributeRoute(next.Object, matchingRoutes, linkGenerationEntries, NullLoggerFactory.Instance);
var context = CreateRouteContext(request);
// Act
await route.RouteAsync(context);
// Assert
Assert.True(context.IsHandled);
if (p1 != null)
{
Assert.Equal(p1, context.RouteData.Values["p1"]);
}
if (p2 != null)
{
Assert.Equal(p2, context.RouteData.Values["p2"]);
}
if (p3 != null)
{
Assert.Equal(p3, context.RouteData.Values["p3"]);
}
}
[Theory]
[InlineData("moo/{p1}.{p2?}", "/moo/foo.")]
[InlineData("moo/{p1}.{p2?}", "/moo/.")]
[InlineData("moo/{p1}.{p2}", "/foo.")]
[InlineData("moo/{p1}.{p2}", "/foo")]
[InlineData("moo/{p1}.{p2}.{p3?}", "/moo/foo.moo.")]
[InlineData("moo/foo.{p2}.{p3?}", "/moo/bar.foo.moo")]
[InlineData("moo/foo.{p2}.{p3?}", "/moo/kungfoo.moo.bar")]
[InlineData("moo/foo.{p2}.{p3?}", "/moo/kungfoo.moo")]
[InlineData("moo/{p1}.{p2}.{p3?}", "/moo/foo")]
[InlineData("{p1}.{p2?}/{p3}", "/foo./bar")]
[InlineData("moo/.{p2?}", "/moo/.")]
[InlineData("{p1}.{p2}/{p3}", "/.foo/bar")]
public async Task AttributeRoute_WithOptionalCompositeParameter_Invalid(
string template,
string request)
{
// Arrange
var expectedRouteGroup = string.Format("{0}&&{1}", 0, template);
// We need to force the creation of a closure in order to avoid an issue with Moq and Roslyn.
var numberOfCalls = 0;
Action<RouteContext> callBack = ctx => { ctx.IsHandled = true; numberOfCalls++; };
var next = new Mock<IRouter>();
next.Setup(r => r.RouteAsync(It.IsAny<RouteContext>()))
.Callback(callBack)
.Returns(Task.FromResult(true))
.Verifiable();
var firstRoute = CreateMatchingEntry(next.Object, template, order: 0);
// We setup the route entries in reverse order of precedence to ensure that when we
// try to route the request, the route with a higher precedence gets tried first.
var matchingRoutes = new[] { firstRoute };
var linkGenerationEntries = Enumerable.Empty<AttributeRouteLinkGenerationEntry>();
var route = new AttributeRoute(next.Object, matchingRoutes, linkGenerationEntries, NullLoggerFactory.Instance);
var context = CreateRouteContext(request);
// Act
await route.RouteAsync(context);
// Assert
Assert.False(context.IsHandled);
}
[Theory]
[InlineData("template/5", "template/{parameter:int}")]
[InlineData("template/5", "template/{parameter}")]
@ -1196,6 +1309,96 @@ namespace Microsoft.AspNet.Mvc.Routing
Assert.Equal("Store", path);
}
public static IEnumerable<object[]> OptionalParamValues
{
get
{
return new object[][]
{
// defaults
// ambient values
// values
new object[]
{
"Test/{val1}/{val2}.{val3?}",
new {val1 = "someval1", val2 = "someval2", val3 = "someval3a"},
new {val3 = "someval3v"},
"Test/someval1/someval2.someval3v",
},
new object[]
{
"Test/{val1}/{val2}.{val3?}",
new {val3 = "someval3a"},
new {val1 = "someval1", val2 = "someval2", val3 = "someval3v" },
"Test/someval1/someval2.someval3v",
},
new object[]
{
"Test/{val1}/{val2}.{val3?}",
null,
new {val1 = "someval1", val2 = "someval2" },
"Test/someval1/someval2",
},
new object[]
{
"Test/{val1}.{val2}.{val3}.{val4?}",
new {val1 = "someval1", val2 = "someval2" },
new {val4 = "someval4", val3 = "someval3" },
"Test/someval1.someval2.someval3.someval4",
},
new object[]
{
"Test/{val1}.{val2}.{val3}.{val4?}",
new {val1 = "someval1", val2 = "someval2" },
new {val3 = "someval3" },
"Test/someval1.someval2.someval3",
},
new object[]
{
"Test/.{val2?}",
null,
new {val2 = "someval2" },
"Test/.someval2",
},
new object[]
{
"Test/.{val2?}",
null,
null,
"Test/",
},
new object[]
{
"Test/{val1}.{val2}",
new {val1 = "someval1", val2 = "someval2" },
new {val3 = "someval3" },
"Test/someval1.someval2?val3=someval3",
},
};
}
}
[Theory]
[MemberData("OptionalParamValues")]
public void AttributeRoute_GenerateLink_Match_WithOptionalParameters(
string template,
object ambientValues,
object values,
string expected)
{
// Arrange
var entry = CreateGenerationEntry(template, null);
var route = CreateAttributeRoute(entry);
var context = CreateVirtualPathContext(values, ambientValues);
// Act
var path = route.GetVirtualPath(context);
// Assert
Assert.Equal(expected, path);
}
[Fact]
public async Task AttributeRoute_CreatesNewRouteData()
{

View File

@ -405,5 +405,35 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests
// Assert
Assert.Equal(HttpStatusCode.NotAcceptable, response.StatusCode);
}
[Fact]
public async Task ProducesAttribute_And_FormatFilterAttribute_Conflicting()
{
// Arrange
var server = TestServer.Create(_provider, _app);
var client = server.CreateClient();
var expectedContentType = MediaTypeHeaderValue.Parse("application/json");
// Act
var response = await client.GetAsync("http://localhost/FormatFilter/MethodWithFormatFilter.json");
// Assert
Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
}
[Fact]
public async Task ProducesAttribute_And_FormatFilterAttribute_Collaborating()
{
// Arrange
var server = TestServer.Create(_provider, _app);
var client = server.CreateClient();
// Act
var response = await client.GetAsync("http://localhost/FormatFilter/MethodWithFormatFilter");
// Assert
var type = response.Content.Headers.ContentType;
var body = await response.Content.ReadAsStringAsync();
}
}
}

View File

@ -0,0 +1,120 @@
// 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 System;
using System.Net;
using System.Threading.Tasks;
using Microsoft.AspNet.Builder;
using Microsoft.AspNet.TestHost;
using Xunit;
namespace Microsoft.AspNet.Mvc.FunctionalTests
{
public class FormatFilterTest
{
private readonly IServiceProvider _services = TestHelper.CreateServices("FormatFilterWebSite");
private readonly Action<IApplicationBuilder> _app = new FormatFilterWebSite.Startup().Configure;
[Fact]
public async Task FormatFilter_NoExtensionInRequest()
{
// Arrange
var server = TestServer.Create(_services, _app);
var client = server.CreateClient();
// Act
var response = await client.GetAsync("http://localhost/FormatFilter/GetProduct/5");
// Assert
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
Assert.Equal(@"{""SampleInt"":5}", await response.Content.ReadAsStringAsync());
}
[Fact]
public async Task FormatFilter_ExtensionInRequest_Default()
{
// Arrange
var server = TestServer.Create(_services, _app);
var client = server.CreateClient();
// Act
var response = await client.GetAsync("http://localhost/FormatFilter/GetProduct/5.json");
// Assert
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
Assert.Equal(@"{""SampleInt"":5}", await response.Content.ReadAsStringAsync());
}
[Fact]
public async Task FormatFilter_ExtensionInRequest_Custom()
{
// Arrange
var server = TestServer.Create(_services, _app);
var client = server.CreateClient();
// Act
var response = await client.GetAsync("http://localhost/FormatFilter/GetProduct/5.custom");
// Assert
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
Assert.Equal(@"SampleInt:5", await response.Content.ReadAsStringAsync());
}
[Fact]
public async Task FormatFilter_ExtensionInRequest_NonExistant()
{
// Arrange
var server = TestServer.Create(_services, _app);
var client = server.CreateClient();
// Act
var response = await client.GetAsync("http://localhost/FormatFilter/GetProduct/5.xml");
// Assert
Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
}
[Fact]
public async Task FormatFilter_And_ProducesFilter_Match()
{
// Arrange
var server = TestServer.Create(_services, _app);
var client = server.CreateClient();
// Act
var response = await client.GetAsync("http://localhost/FormatFilter/ProducesMethod/5.json");
// Assert
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
Assert.Equal(@"{""SampleInt"":5}", await response.Content.ReadAsStringAsync());
}
[Fact]
public async Task FormatFilter_And_ProducesFilter_Conflict()
{
// Arrange
var server = TestServer.Create(_services, _app);
var client = server.CreateClient();
// Act
var response = await client.GetAsync("http://localhost/FormatFilter/ProducesMethod/5.xml");
// Assert
Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
}
[Fact]
public async Task FormatFilter_And_OverrideProducesFilter()
{
// Arrange
var server = TestServer.Create(_services, _app);
var client = server.CreateClient();
// Act
var response = await client.GetAsync("http://localhost/ProducesDerived/ReturnClassName.json");
// Assert
Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
}
}
}

View File

@ -44,7 +44,8 @@
"MvcTagHelpersWebSite": "1.0.0",
"Microsoft.Framework.ConfigurationModel.Json": "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": {
"test": "xunit.runner.kre"

View File

@ -0,0 +1,18 @@
// 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 ConnegWebSite;
using Microsoft.AspNet.Mvc;
namespace ConnegWebsite
{
[Produces("application/FormatFilterController")]
public class FormatFilterController : Controller
{
[FormatFilter]
public User MethodWithFormatFilter()
{
return new User() { Name = "Joe", Address = "1 abc way" };
}
}
}

View File

@ -37,6 +37,6 @@ namespace ConnegWebSite
public virtual string ReturnClassNameContentTypeOnDerivedAction()
{
return "ProducesContentBaseController";
}
}
}
}

View File

@ -39,6 +39,6 @@ namespace ConnegWebSite
{
// should be written using the content defined at derived class's class.
return "ProducesContentOnClassController";
}
}
}
}

View File

@ -31,7 +31,7 @@ namespace ConnegWebSite
app.UseMvc(routes =>
{
routes.MapRoute("ActionAsMethod", "{controller}/{action}",
defaults: new { controller = "Home", action = "Index" });
defaults: new { controller = "Home", action = "Index" });
});
}
}

View File

@ -15,4 +15,9 @@
<DevelopmentServerPort>49641</DevelopmentServerPort>
</PropertyGroup>
<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>

View File

@ -5,6 +5,7 @@ using System.Security.Claims;
using Microsoft.AspNet.Builder;
using Microsoft.AspNet.Mvc;
using Microsoft.AspNet.Security;
using Microsoft.AspNet.Routing;
using Microsoft.Framework.DependencyInjection;
namespace FiltersWebSite

View File

@ -0,0 +1,22 @@
// 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
{
[FormatFilter]
public class FormatFilterController : Controller
{
public Product GetProduct(int id)
{
return new Product() { SampleInt = id };
}
[Produces("application/custom", "application/json", "text/json")]
public Product ProducesMethod(int id)
{
return new Product() { SampleInt = id };
}
}
}

View File

@ -0,0 +1,17 @@
// 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";
}
}
}

View File

@ -0,0 +1,18 @@
// 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
{
[Produces("application/custom_ProducesOverrideController")]
public class ProducesOverrideController : ProducesBaseController
{
[FormatFilter]
public override string ReturnClassName()
{
// should be written using the content defined at base class's action.
return "ProducesOverrideController";
}
}
}

View File

@ -0,0 +1,43 @@
// 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 System.Text;
using System.Threading.Tasks;
using Microsoft.AspNet.Http;
using Microsoft.AspNet.Mvc;
using Microsoft.Net.Http.Headers;
namespace FormatFilterWebSite
{
public class CustomFormatter : OutputFormatter
{
public string ContentType { get; private set; }
public CustomFormatter(string contentType)
{
ContentType = contentType;
SupportedMediaTypes.Add(MediaTypeHeaderValue.Parse(contentType));
SupportedEncodings.Add(Encoding.GetEncoding("utf-8"));
}
public override bool CanWriteResult(OutputFormatterContext context, MediaTypeHeaderValue contentType)
{
if (base.CanWriteResult(context, contentType))
{
var actionReturn = context.Object as Product;
if (actionReturn != null)
{
return true;
}
}
return false;
}
public override async Task WriteResponseBodyAsync(OutputFormatterContext context)
{
var response = context.ActionContext.HttpContext.Response;
response.ContentType = ContentType + ";charset=utf-8";
await response.WriteAsync(context.Object.ToString());
}
}
}

View File

@ -0,0 +1,24 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">14.0</VisualStudioVersion>
<VSToolsPath Condition="'$(VSToolsPath)' == ''">$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)</VSToolsPath>
</PropertyGroup>
<Import Project="$(VSToolsPath)\AspNet\Microsoft.Web.AspNet.Props" Condition="'$(VSToolsPath)' != ''" />
<PropertyGroup Label="Globals">
<ProjectGuid>ac9be567-540e-4c70-90c2-aaf021307a80</ProjectGuid>
<RootNamespace>FormatFilterWebSite</RootNamespace>
<BaseIntermediateOutputPath Condition="'$(BaseIntermediateOutputPath)'=='' ">..\..\..\artifacts\obj\$(MSBuildProjectName)</BaseIntermediateOutputPath>
<OutputPath Condition="'$(OutputPath)'=='' ">..\..\..\artifacts\bin\$(MSBuildProjectName)\</OutputPath>
</PropertyGroup>
<PropertyGroup>
<SchemaVersion>2.0</SchemaVersion>
<DevelopmentServerPort>51135</DevelopmentServerPort>
</PropertyGroup>
<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>

View File

@ -0,0 +1,14 @@
using System;
namespace FormatFilterWebSite
{
public class Product
{
public int SampleInt { get; set; }
public override string ToString()
{
return "SampleInt:" + SampleInt;
}
}
}

View File

@ -0,0 +1,43 @@
// 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.Builder;
using Microsoft.AspNet.Mvc;
using Microsoft.AspNet.Routing;
using Microsoft.Framework.DependencyInjection;
using Microsoft.Net.Http.Headers;
namespace FormatFilterWebSite
{
public class Startup
{
public void Configure(IApplicationBuilder app)
{
var configuration = app.GetTestConfiguration();
app.UseServices(services =>
{
services.AddMvc(configuration);
services.Configure<MvcOptions>(options =>
{
var formatFilter = new FormatFilterAttribute();
options.Filters.Add(formatFilter);
var customFormatter = new CustomFormatter("application/custom");
options.OutputFormatters.Add(customFormatter);
options.FormatterMappings.SetFormatMapping(
"custom",
MediaTypeHeaderValue.Parse("application/custom"));
});
});
app.UseMvc(routes =>
{
routes.MapRoute("formatroute",
"{controller}/{action}/{id}.{format?}",
new { controller = "Home", action = "Index" });
});
}
}
}

View File

@ -0,0 +1,19 @@
{
"commands": {
"web": "Microsoft.AspNet.Hosting server=Microsoft.AspNet.Server.WebListener server.urls=http://localhost:5001",
"kestrel": "Microsoft.AspNet.Hosting --server Kestrel --server.urls http://localhost:5000"
},
"dependencies": {
"Kestrel": "1.0.0-*",
"Microsoft.AspNet.Mvc": "6.0.0-*",
"Microsoft.AspNet.Mvc.TestConfiguration": "1.0.0",
"Microsoft.AspNet.Server.IIS": "1.0.0-*",
"Microsoft.AspNet.Server.WebListener": "1.0.0-*",
"Microsoft.AspNet.StaticFiles": "1.0.0-*"
},
"frameworks": {
"aspnet50": { },
"aspnetcore50": { }
},
"webroot": "wwwroot"
}