Add JSON formatter support

This commit is contained in:
Yishai Galatzer 2014-01-15 21:22:01 -08:00
parent 99f612e5cb
commit bd2e434da9
12 changed files with 220 additions and 17 deletions

View File

@ -1,6 +1,8 @@
using System; using System;
using System.Diagnostics.CodeAnalysis; using System.Diagnostics.CodeAnalysis;
using System.Diagnostics.Contracts;
using System.Linq; using System.Linq;
using System.Linq.Expressions;
using System.Reflection; using System.Reflection;
namespace Microsoft.AspNet.CoreServices namespace Microsoft.AspNet.CoreServices
@ -82,5 +84,23 @@ namespace Microsoft.AspNet.CoreServices
{ {
return constructor.IsPublic && constructor.GetParameters().Length != 0; return constructor.IsPublic && constructor.GetParameters().Length != 0;
} }
public static Func<TBase> Create<TBase>(Type instanceType) where TBase : class
{
Contract.Assert(instanceType != null);
NewExpression newInstanceExpression = Expression.New(instanceType);
return Expression.Lambda<Func<TBase>>(newInstanceExpression).Compile();
}
public static Func<TInstance> Create<TInstance>() where TInstance : class
{
return Create<TInstance>(typeof(TInstance));
}
public static Func<object> Create(Type instanceType)
{
Contract.Assert(instanceType != null);
return Create<object>(instanceType);
}
} }
} }

View File

@ -1,12 +1,50 @@
using System.Net.Http; using Microsoft.AspNet.CoreServices;
using System;
using System.Collections.Generic;
using System.Net;
using System.Net.Http;
using System.Net.Http.Formatting; using System.Net.Http.Formatting;
using System.Linq;
namespace Microsoft.AspNet.Mvc namespace Microsoft.AspNet.Mvc
{ {
public class ActionResultFactory : IActionResultFactory public class ActionResultFactory : IActionResultFactory
{ {
public IActionResult CreateActionResult(object actionReturnValue) public IActionResult CreateActionResult(Type declaredReturnType, object actionReturnValue, RequestContext requestContext)
{ {
// optimize common path
IActionResult actionResult = actionReturnValue as IActionResult;
if (actionResult != null)
{
return actionResult;
}
bool isDeclaredTypeActionResult = typeof(IActionResult).IsAssignableFrom(declaredReturnType);
bool isDeclaredTypeResponseMessage = typeof(HttpResponseMessage).IsAssignableFrom(declaredReturnType);
if ((isDeclaredTypeActionResult || isDeclaredTypeResponseMessage) && actionReturnValue == null)
{
throw new InvalidOperationException("Cannot return null from an action method declaring IActionResult or HttpResponseMessage");
}
if (declaredReturnType == null)
{
throw new InvalidOperationException("Declared type must be passed");
}
if (declaredReturnType.IsGenericParameter)
{
// This can happen if somebody declares an action method as:
// public T Get<T>() { }
throw new InvalidOperationException("HttpActionDescriptor_NoConverterForGenericParamterTypeExists");
}
if (declaredReturnType.IsAssignableFrom(typeof(void)))
{
return new NoContentResult();
}
var responseMessage = actionReturnValue as HttpResponseMessage; var responseMessage = actionReturnValue as HttpResponseMessage;
if (responseMessage != null) if (responseMessage != null)
{ {
@ -17,19 +55,18 @@ namespace Microsoft.AspNet.Mvc
{ {
return new ContentResult return new ContentResult
{ {
Content = (string)actionReturnValue ContentType = "text/plain",
Content = (string)actionReturnValue,
}; };
} }
// all other object types are treated as an http response message action result // TODO: this needs to get injected
var content = new ObjectContent(actionReturnValue.GetType(), IOwinContentNegotiator contentNegotiator = new DefaultContentNegotiator();
actionReturnValue,
new JsonMediaTypeFormatter());
return new HttpResponseMessageActionResult(new HttpResponseMessage // TODO: inject the formatters
{ IEnumerable<MediaTypeFormatter> formatters = requestContext.Formatters;
Content = content
}); return new NegotiatedContentResult(HttpStatusCode.OK, declaredReturnType, actionReturnValue, contentNegotiator, requestContext.HttpContext, formatters);
} }
} }
} }

View File

@ -0,0 +1,104 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
using System.Diagnostics.Contracts;
using Microsoft.Owin;
using System.Net;
using System.Net.Http;
using System.Net.Http.Formatting;
using System.Threading;
using System.Threading.Tasks;
using System.Web.Http.Properties;
using System.Linq;
namespace Microsoft.AspNet.Mvc
{
/// <summary>Represents an action result that performs content negotiation.</summary>
/// <typeparam name="T">The type of content in the entity body.</typeparam>
internal class NegotiatedContentResult : IActionResult
{
/// <summary>
/// Initializes a new instance of the <see cref="NegotiatedContentResult{T}"/> class with the values provided.
/// </summary>
/// <param name="statusCode">The HTTP status code for the response message.</param>
/// <param name="content">The content value to negotiate and format in the entity body.</param>
/// <param name="contentNegotiator">The content negotiator to handle content negotiation.</param>
/// <param name="request">The request message which led to this result.</param>
/// <param name="formatters">The formatters to use to negotiate and format the content.</param>
public NegotiatedContentResult(HttpStatusCode statusCode,
Type declaredType,
object content,
IOwinContentNegotiator contentNegotiator,
IOwinContext owinContext,
IEnumerable<MediaTypeFormatter> formatters)
{
Contract.Assert(content != null);
Contract.Assert(declaredType != null);
Contract.Assert(owinContext != null);
Contract.Assert(formatters != null);
StatusCode = statusCode;
DeclaredType = declaredType;
Content = content;
CurrentOwinContext = owinContext;
Formatters = formatters;
ContentNegotiator = contentNegotiator;
}
/// <summary>Gets the HTTP status code for the response message.</summary>
public HttpStatusCode StatusCode { get; private set; }
public Type DeclaredType { get; private set; }
/// <summary>Gets the content value to negotiate and format in the entity body.</summary>
public object Content { get; private set; }
/// <summary>Gets the content negotiator to handle content negotiation.</summary>
public IOwinContentNegotiator ContentNegotiator { get; private set; }
/// <summary>Gets the request message which led to this result.</summary>
public IOwinContext CurrentOwinContext { get; private set; }
/// <summary>Gets the formatters to use to negotiate and format the content.</summary>
public IEnumerable<MediaTypeFormatter> Formatters { get; private set; }
/// <inheritdoc />
public virtual Task ExecuteResultAsync(RequestContext context)
{
// Run content negotiation.
ContentNegotiationResult result = ContentNegotiator.Negotiate(DeclaredType, CurrentOwinContext, Formatters);
if (result == null)
{
// A null result from content negotiation indicates that the response should be a 406.
CurrentOwinContext.Response.StatusCode = (int)HttpStatusCode.NotAcceptable;
return Task.FromResult(false);
}
else
{
IOwinResponse response = CurrentOwinContext.Response;
response.StatusCode = (int)StatusCode;
Contract.Assert(result.Formatter != null);
var objectContent = new ObjectContent(DeclaredType, Content, result.Formatter, result.MediaType);
// Copy non-content headers
IDictionary<string, string[]> responseHeaders = response.Headers;
foreach (KeyValuePair<string, string[]> header in response.Headers)
{
responseHeaders[header.Key] = header.Value.AsArray();
}
// Copy content headers
foreach (KeyValuePair<string, IEnumerable<string>> contentHeader in objectContent.Headers)
{
responseHeaders[contentHeader.Key] = contentHeader.Value.AsArray();
}
return objectContent.CopyToAsync(response.Body);
}
}
}
}

View File

@ -48,7 +48,7 @@ namespace Microsoft.AspNet.Mvc
object actionReturnValue = method.Invoke(controller, null); object actionReturnValue = method.Invoke(controller, null);
// If it's not already an IActionResult then call into the factory // If it's not already an IActionResult then call into the factory
var actionResult = actionReturnValue as IActionResult ?? _actionResultFactory.CreateActionResult(actionReturnValue); var actionResult = actionReturnValue as IActionResult ?? _actionResultFactory.CreateActionResult(method.ReturnType, actionReturnValue, _requestContext);
return actionResult.ExecuteResultAsync(_requestContext); return actionResult.ExecuteResultAsync(_requestContext);
} }

View File

@ -1,8 +1,10 @@
 using Microsoft.Owin;
using System;
namespace Microsoft.AspNet.Mvc namespace Microsoft.AspNet.Mvc
{ {
public interface IActionResultFactory public interface IActionResultFactory
{ {
IActionResult CreateActionResult(object actionReturnValue); IActionResult CreateActionResult(Type declaredReturnType, object actionReturnValue, RequestContext requestContext);
} }
} }

View File

@ -53,8 +53,17 @@
<Compile Include="ActionInvokerProvider.cs" /> <Compile Include="ActionInvokerProvider.cs" />
<Compile Include="ActionResultFactory.cs" /> <Compile Include="ActionResultFactory.cs" />
<Compile Include="ActionResultHelper.cs" /> <Compile Include="ActionResultHelper.cs" />
<Compile Include="ContentResult.cs" /> <Compile Include="ActionResults\NoContentResult.cs" />
<Compile Include="ActionResults\NegotiatedContentResult.cs" />
<Compile Include="ActionResults\ContentResult.cs" />
<Compile Include="ActionResults\ObjectContent.cs" />
<Compile Include="ControllerBasedActionDescriptor.cs" /> <Compile Include="ControllerBasedActionDescriptor.cs" />
<Compile Include="DefaultContentNegotiator.cs" />
<Compile Include="Extensions\IEnumerableExtensions.cs" />
<Compile Include="Formatters\JQeryMvcForUrlEncodedFormatter.cs" />
<Compile Include="FormattingUtilities.cs" />
<Compile Include="Extensions\TypeExtensions.cs" />
<Compile Include="IOwinContentNegotiator.cs" />
<Compile Include="RequestContext.cs" /> <Compile Include="RequestContext.cs" />
<Compile Include="EmptyResult.cs" /> <Compile Include="EmptyResult.cs" />
<Compile Include="HttpResponseMessageActionResult.cs" /> <Compile Include="HttpResponseMessageActionResult.cs" />

View File

@ -1,5 +1,6 @@
using System; using System;
using Microsoft.AspNet.CoreServices; using Microsoft.AspNet.CoreServices;
using System.Net.Http.Formatting;
namespace Microsoft.AspNet.Mvc namespace Microsoft.AspNet.Mvc
{ {
@ -18,6 +19,7 @@ namespace Microsoft.AspNet.Mvc
callback(typeof(IActionInvokerFactory), typeof(ActionInvokerFactory)); callback(typeof(IActionInvokerFactory), typeof(ActionInvokerFactory));
callback(typeof(IActionResultHelper), typeof(ActionResultHelper)); callback(typeof(IActionResultHelper), typeof(ActionResultHelper));
callback(typeof(IActionResultFactory), typeof(ActionResultFactory)); callback(typeof(IActionResultFactory), typeof(ActionResultFactory));
callback(typeof(IContentNegotiator), typeof(DefaultContentNegotiator));
// TODO: Should be many // TODO: Should be many
callback(typeof(IActionDescriptorProvider), typeof(ActionDescriptorProvider)); callback(typeof(IActionDescriptorProvider), typeof(ActionDescriptorProvider));

View File

@ -1,6 +1,7 @@
using System; using System;
using Microsoft.AspNet.Mvc.Routing; using Microsoft.AspNet.Mvc.Routing;
using Microsoft.Owin; using Microsoft.Owin;
using System.Net.Http.Formatting;
namespace Microsoft.AspNet.Mvc namespace Microsoft.AspNet.Mvc
{ {
@ -20,10 +21,23 @@ namespace Microsoft.AspNet.Mvc
HttpContext = context; HttpContext = context;
RouteData = routeData; RouteData = routeData;
// todo: inject
InjectFormatters();
}
private void InjectFormatters()
{
Formatters = new MediaTypeFormatterCollection();
Formatters.Add(new JsonMediaTypeFormatter());
//Formatters.Add(new XmlMediaTypeFormatter());
//Formatters.Add(new JQueryMvcFormUrlEncodedFormatter());
} }
public virtual IRouteData RouteData { get; set; } public virtual IRouteData RouteData { get; set; }
public virtual IOwinContext HttpContext { get; set; } public virtual IOwinContext HttpContext { get; set; }
public virtual MediaTypeFormatterCollection Formatters { get; private set; }
} }
} }

View File

@ -37,9 +37,15 @@ namespace MvcSample
return responseMessage; return responseMessage;
} }
public IActionResult MyView() public User User()
{ {
return Result.View(); User user = new User()
{
Name = "My name",
Address = "My address"
};
return user;
} }
} }
} }

View File

@ -0,0 +1,8 @@
namespace MvcSample
{
public class User
{
public string Name { get; set; }
public string Address { get; set; }
}
}

View File

@ -67,6 +67,7 @@
<ItemGroup> <ItemGroup>
<Compile Include="Home2Controller.cs" /> <Compile Include="Home2Controller.cs" />
<Compile Include="HomeController.cs" /> <Compile Include="HomeController.cs" />
<Compile Include="Models\Class1.cs" />
<Compile Include="Properties\AssemblyInfo.cs" /> <Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="Startup.cs" /> <Compile Include="Startup.cs" />
</ItemGroup> </ItemGroup>