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.Diagnostics.CodeAnalysis;
using System.Diagnostics.Contracts;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
namespace Microsoft.AspNet.CoreServices
@ -82,5 +84,23 @@ namespace Microsoft.AspNet.CoreServices
{
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.Linq;
namespace Microsoft.AspNet.Mvc
{
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;
if (responseMessage != null)
{
@ -17,19 +55,18 @@ namespace Microsoft.AspNet.Mvc
{
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
var content = new ObjectContent(actionReturnValue.GetType(),
actionReturnValue,
new JsonMediaTypeFormatter());
// TODO: this needs to get injected
IOwinContentNegotiator contentNegotiator = new DefaultContentNegotiator();
return new HttpResponseMessageActionResult(new HttpResponseMessage
{
Content = content
});
// TODO: inject the formatters
IEnumerable<MediaTypeFormatter> formatters = requestContext.Formatters;
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);
// 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);
}

View File

@ -1,8 +1,10 @@

using Microsoft.Owin;
using System;
namespace Microsoft.AspNet.Mvc
{
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="ActionResultFactory.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="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="EmptyResult.cs" />
<Compile Include="HttpResponseMessageActionResult.cs" />

View File

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

View File

@ -1,6 +1,7 @@
using System;
using Microsoft.AspNet.Mvc.Routing;
using Microsoft.Owin;
using System.Net.Http.Formatting;
namespace Microsoft.AspNet.Mvc
{
@ -20,10 +21,23 @@ namespace Microsoft.AspNet.Mvc
HttpContext = context;
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 IOwinContext HttpContext { get; set; }
public virtual MediaTypeFormatterCollection Formatters { get; private set; }
}
}

View File

@ -37,9 +37,15 @@ namespace MvcSample
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>
<Compile Include="Home2Controller.cs" />
<Compile Include="HomeController.cs" />
<Compile Include="Models\Class1.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="Startup.cs" />
</ItemGroup>