diff --git a/Microsoft.AspNet.CoreServices/ActivatorUtilities.cs b/Microsoft.AspNet.CoreServices/ActivatorUtilities.cs index 1148861b82..45f4222569 100644 --- a/Microsoft.AspNet.CoreServices/ActivatorUtilities.cs +++ b/Microsoft.AspNet.CoreServices/ActivatorUtilities.cs @@ -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 Create(Type instanceType) where TBase : class + { + Contract.Assert(instanceType != null); + NewExpression newInstanceExpression = Expression.New(instanceType); + return Expression.Lambda>(newInstanceExpression).Compile(); + } + + public static Func Create() where TInstance : class + { + return Create(typeof(TInstance)); + } + + public static Func Create(Type instanceType) + { + Contract.Assert(instanceType != null); + return Create(instanceType); + } } } diff --git a/Microsoft.AspNet.Mvc/ActionResultFactory.cs b/Microsoft.AspNet.Mvc/ActionResultFactory.cs index a286be13be..485b1ab995 100644 --- a/Microsoft.AspNet.Mvc/ActionResultFactory.cs +++ b/Microsoft.AspNet.Mvc/ActionResultFactory.cs @@ -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() { } + 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 formatters = requestContext.Formatters; + + return new NegotiatedContentResult(HttpStatusCode.OK, declaredReturnType, actionReturnValue, contentNegotiator, requestContext.HttpContext, formatters); } } } diff --git a/Microsoft.AspNet.Mvc/ContentResult.cs b/Microsoft.AspNet.Mvc/ActionResults/ContentResult.cs similarity index 100% rename from Microsoft.AspNet.Mvc/ContentResult.cs rename to Microsoft.AspNet.Mvc/ActionResults/ContentResult.cs diff --git a/Microsoft.AspNet.Mvc/ActionResults/NegotiatedContentResult.cs b/Microsoft.AspNet.Mvc/ActionResults/NegotiatedContentResult.cs new file mode 100644 index 0000000000..3407b0e272 --- /dev/null +++ b/Microsoft.AspNet.Mvc/ActionResults/NegotiatedContentResult.cs @@ -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 +{ + /// Represents an action result that performs content negotiation. + /// The type of content in the entity body. + internal class NegotiatedContentResult : IActionResult + { + /// + /// Initializes a new instance of the class with the values provided. + /// + /// The HTTP status code for the response message. + /// The content value to negotiate and format in the entity body. + /// The content negotiator to handle content negotiation. + /// The request message which led to this result. + /// The formatters to use to negotiate and format the content. + public NegotiatedContentResult(HttpStatusCode statusCode, + Type declaredType, + object content, + IOwinContentNegotiator contentNegotiator, + IOwinContext owinContext, + IEnumerable 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; + } + + /// Gets the HTTP status code for the response message. + public HttpStatusCode StatusCode { get; private set; } + + public Type DeclaredType { get; private set; } + + /// Gets the content value to negotiate and format in the entity body. + public object Content { get; private set; } + + /// Gets the content negotiator to handle content negotiation. + public IOwinContentNegotiator ContentNegotiator { get; private set; } + + /// Gets the request message which led to this result. + public IOwinContext CurrentOwinContext { get; private set; } + + /// Gets the formatters to use to negotiate and format the content. + public IEnumerable Formatters { get; private set; } + + /// + 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 responseHeaders = response.Headers; + foreach (KeyValuePair header in response.Headers) + { + responseHeaders[header.Key] = header.Value.AsArray(); + } + + // Copy content headers + foreach (KeyValuePair> contentHeader in objectContent.Headers) + { + responseHeaders[contentHeader.Key] = contentHeader.Value.AsArray(); + } + + return objectContent.CopyToAsync(response.Body); + } + } + } +} diff --git a/Microsoft.AspNet.Mvc/ControllerActionInvoker.cs b/Microsoft.AspNet.Mvc/ControllerActionInvoker.cs index 909a525054..15346c509e 100644 --- a/Microsoft.AspNet.Mvc/ControllerActionInvoker.cs +++ b/Microsoft.AspNet.Mvc/ControllerActionInvoker.cs @@ -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); } diff --git a/Microsoft.AspNet.Mvc/IActionResultFactory.cs b/Microsoft.AspNet.Mvc/IActionResultFactory.cs index b3894aa450..ab16c71143 100644 --- a/Microsoft.AspNet.Mvc/IActionResultFactory.cs +++ b/Microsoft.AspNet.Mvc/IActionResultFactory.cs @@ -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); } } diff --git a/Microsoft.AspNet.Mvc/Microsoft.AspNet.Mvc.csproj b/Microsoft.AspNet.Mvc/Microsoft.AspNet.Mvc.csproj index f2ba7b00ad..2ea0f9defd 100644 --- a/Microsoft.AspNet.Mvc/Microsoft.AspNet.Mvc.csproj +++ b/Microsoft.AspNet.Mvc/Microsoft.AspNet.Mvc.csproj @@ -53,8 +53,17 @@ - + + + + + + + + + + diff --git a/Microsoft.AspNet.Mvc/MvcServices.cs b/Microsoft.AspNet.Mvc/MvcServices.cs index 1ca4a2d87f..5b5acbfdce 100644 --- a/Microsoft.AspNet.Mvc/MvcServices.cs +++ b/Microsoft.AspNet.Mvc/MvcServices.cs @@ -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)); diff --git a/Microsoft.AspNet.Mvc/RequestContext.cs b/Microsoft.AspNet.Mvc/RequestContext.cs index ec3429d9cc..7f55a5112d 100644 --- a/Microsoft.AspNet.Mvc/RequestContext.cs +++ b/Microsoft.AspNet.Mvc/RequestContext.cs @@ -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; } } } diff --git a/MvcSample/HomeController.cs b/MvcSample/HomeController.cs index 11efd273ea..c03b32c9dd 100644 --- a/MvcSample/HomeController.cs +++ b/MvcSample/HomeController.cs @@ -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; } } } \ No newline at end of file diff --git a/MvcSample/Models/Class1.cs b/MvcSample/Models/Class1.cs new file mode 100644 index 0000000000..cc453843e6 --- /dev/null +++ b/MvcSample/Models/Class1.cs @@ -0,0 +1,8 @@ +namespace MvcSample +{ + public class User + { + public string Name { get; set; } + public string Address { get; set; } + } +} \ No newline at end of file diff --git a/MvcSample/MvcSample.csproj b/MvcSample/MvcSample.csproj index 557f31b484..04f0576d4e 100644 --- a/MvcSample/MvcSample.csproj +++ b/MvcSample/MvcSample.csproj @@ -67,6 +67,7 @@ +