-
Notifications
You must be signed in to change notification settings - Fork 11
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #322 from ServiceComposer/set-action-result
MVC action results support
- Loading branch information
Showing
11 changed files
with
315 additions
and
42 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
<!-- | ||
GENERATED FILE - DO NOT EDIT | ||
This file was generated by [MarkdownSnippets](https://github.com/SimonCropp/MarkdownSnippets). | ||
Source File: /docs/action-results.source.md | ||
To change this file edit the source file and then run MarkdownSnippets. | ||
--> | ||
|
||
# ASP.Net MVC Action results | ||
|
||
MVC Action results support allow composition handlers to set custom response results for specific scenarios, like for example, handling bad requests or validation error thoat would nornmally require throwing an exception. Setting a custom action result is done by using the `SetActionResult()` `HttpRequest` extension method: | ||
|
||
<!-- snippet: net-core-3x-action-results --> | ||
<a id='snippet-net-core-3x-action-results'></a> | ||
```cs | ||
public class UseSetActionResultHandler : ICompositionRequestsHandler | ||
{ | ||
[HttpGet("/product/{id}")] | ||
public Task Handle(HttpRequest request) | ||
{ | ||
var id = request.RouteValues["id"]; | ||
|
||
//validate the id format | ||
var problems = new ValidationProblemDetails(new Dictionary<string, string[]>() | ||
{ | ||
{ "Id", new []{ "The supplied id does not respect the identifier format." } } | ||
}); | ||
var result = new BadRequestObjectResult(problems); | ||
|
||
request.SetActionResult(result); | ||
|
||
return Task.CompletedTask; | ||
} | ||
} | ||
``` | ||
<sup><a href='/src/Snippets.NetCore3x/ActionResult/UseSetActionResultHandler.cs#L10-L31' title='Snippet source file'>snippet source</a> | <a href='#snippet-net-core-3x-action-results' title='Start of snippet'>anchor</a></sup> | ||
<!-- endSnippet --> | ||
|
||
Using MVC action results require enabling output formatters support: | ||
|
||
<!-- snippet: net-core-3x-action-results-required-config --> | ||
<a id='snippet-net-core-3x-action-results-required-config'></a> | ||
```cs | ||
services.AddViewModelComposition(options => | ||
{ | ||
options.ResponseSerialization.UseOutputFormatters = true; | ||
}); | ||
``` | ||
<sup><a href='/src/Snippets.NetCore3x/ActionResult/UseSetActionResultHandler.cs#L37-L42' title='Snippet source file'>snippet source</a> | <a href='#snippet-net-core-3x-action-results-required-config' title='Start of snippet'>anchor</a></sup> | ||
<!-- endSnippet --> | ||
|
||
Note: ServiceComposer supports only one action result per request. If two or more composition handlers try to set action results, only the frst one will succeed and subsequent requests will be ignored. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
# ASP.Net MVC Action results | ||
|
||
MVC Action results support allow composition handlers to set custom response results for specific scenarios, like for example, handling bad requests or validation error thoat would nornmally require throwing an exception. Setting a custom action result is done by using the `SetActionResult()` `HttpRequest` extension method: | ||
|
||
snippet: net-core-3x-action-results | ||
|
||
Using MVC action results require enabling output formatters support: | ||
|
||
snippet: net-core-3x-action-results-required-config | ||
|
||
Note: ServiceComposer supports only one action result per request. If two or more composition handlers try to set action results, only the frst one will succeed and subsequent requests will be ignored. |
129 changes: 129 additions & 0 deletions
129
src/ServiceComposer.AspNetCore.Endpoints.Tests/When_setting_action_result.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,129 @@ | ||
using System; | ||
using System.Collections.Generic; | ||
using System.Net; | ||
using System.Threading.Tasks; | ||
using Microsoft.AspNetCore.Builder; | ||
using Microsoft.AspNetCore.Http; | ||
using Microsoft.AspNetCore.Mvc; | ||
using Microsoft.Extensions.DependencyInjection; | ||
using Newtonsoft.Json.Linq; | ||
using ServiceComposer.AspNetCore.Testing; | ||
using Xunit; | ||
|
||
namespace ServiceComposer.AspNetCore.Endpoints.Tests | ||
{ | ||
public class When_setting_action_result | ||
{ | ||
const string expectedError = "I'm not sure I like the Id property value"; | ||
|
||
class TestGetIntegerHandler : ICompositionRequestsHandler | ||
{ | ||
class Model | ||
{ | ||
[FromRoute]public int id { get; set; } | ||
} | ||
|
||
[HttpGet("/sample/{id}")] | ||
public async Task Handle(HttpRequest request) | ||
{ | ||
var model = await request.Bind<Model>(); | ||
|
||
var problems = new ValidationProblemDetails(new Dictionary<string, string[]>() | ||
{ | ||
{ "Id", new []{ expectedError } } | ||
}); | ||
var result = new BadRequestObjectResult(problems); | ||
|
||
request.SetActionResult(result); | ||
} | ||
} | ||
|
||
class TestGetStringHandler : ICompositionRequestsHandler | ||
{ | ||
[HttpGet("/sample/{id}")] | ||
public Task Handle(HttpRequest request) | ||
{ | ||
var vm = request.GetComposedResponseModel(); | ||
vm.AString = "sample"; | ||
return Task.CompletedTask; | ||
} | ||
} | ||
|
||
[Fact] | ||
public async Task Returns_expected_bad_request_using_output_formatters() | ||
{ | ||
// Arrange | ||
var client = new SelfContainedWebApplicationFactoryWithWebHost<Dummy> | ||
( | ||
configureServices: services => | ||
{ | ||
services.AddViewModelComposition(options => | ||
{ | ||
options.AssemblyScanner.Disable(); | ||
options.RegisterCompositionHandler<TestGetStringHandler>(); | ||
options.RegisterCompositionHandler<TestGetIntegerHandler>(); | ||
options.ResponseSerialization.UseOutputFormatters = true; | ||
}); | ||
services.AddRouting(); | ||
services.AddControllers() | ||
.AddNewtonsoftJson(); | ||
}, | ||
configure: app => | ||
{ | ||
app.UseRouting(); | ||
app.UseEndpoints(builder => builder.MapCompositionHandlers()); | ||
} | ||
).CreateClient(); | ||
|
||
// Act | ||
var response = await client.GetAsync("/sample/1"); | ||
|
||
// Assert | ||
Assert.False(response.IsSuccessStatusCode); | ||
Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode); | ||
|
||
var responseString = await response.Content.ReadAsStringAsync(); | ||
dynamic responseObj = JObject.Parse(responseString); | ||
|
||
dynamic errors = responseObj.errors; | ||
var idErrors = (JArray)errors["Id"]; | ||
|
||
var error = idErrors[0].Value<string>(); | ||
|
||
Assert.Equal(expectedError, error); | ||
} | ||
|
||
[Fact] | ||
public async Task Throws_if_output_formatters_are_not_enabled() | ||
{ | ||
await Assert.ThrowsAsync<NotSupportedException>(async () => | ||
{ | ||
// Arrange | ||
var client = new SelfContainedWebApplicationFactoryWithWebHost<Dummy> | ||
( | ||
configureServices: services => | ||
{ | ||
services.AddViewModelComposition(options => | ||
{ | ||
options.AssemblyScanner.Disable(); | ||
options.RegisterCompositionHandler<TestGetStringHandler>(); | ||
options.RegisterCompositionHandler<TestGetIntegerHandler>(); | ||
options.ResponseSerialization.UseOutputFormatters = false; | ||
}); | ||
services.AddRouting(); | ||
services.AddControllers() | ||
.AddNewtonsoftJson(); | ||
}, | ||
configure: app => | ||
{ | ||
app.UseRouting(); | ||
app.UseEndpoints(builder => builder.MapCompositionHandlers()); | ||
} | ||
).CreateClient(); | ||
// Act | ||
var response = await client.GetAsync("/sample/1"); | ||
}); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
45 changes: 45 additions & 0 deletions
45
src/ServiceComposer.AspNetCore/HttpContextActionResultExtensions.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
#if NETCOREAPP3_1 || NET5_0 | ||
using Microsoft.AspNetCore.Http; | ||
using Microsoft.AspNetCore.Mvc; | ||
using Microsoft.AspNetCore.Mvc.Abstractions; | ||
using Microsoft.AspNetCore.Routing; | ||
using System; | ||
using System.Threading.Tasks; | ||
|
||
namespace ServiceComposer.AspNetCore | ||
{ | ||
static class HttpContextActionResultExtensions | ||
{ | ||
private static readonly RouteData EmptyRouteData = new RouteData(); | ||
|
||
private static readonly ActionDescriptor EmptyActionDescriptor = new ActionDescriptor(); | ||
|
||
/// <summary> | ||
/// Write a model to the response. | ||
/// </summary> | ||
public static Task WriteModelAsync<TModel>(this HttpContext context, TModel model) | ||
{ | ||
var result = new ObjectResult(model) | ||
{ | ||
DeclaredType = typeof(TModel) | ||
}; | ||
|
||
return context.ExecuteResultAsync(result); | ||
} | ||
|
||
/// <summary> | ||
/// Write any action result to the response. | ||
/// </summary> | ||
public static Task ExecuteResultAsync(this HttpContext context, IActionResult result) | ||
{ | ||
if (context == null) throw new ArgumentNullException(nameof(context)); | ||
if (result == null) throw new ArgumentNullException(nameof(result)); | ||
|
||
var routeData = context.GetRouteData() ?? EmptyRouteData; | ||
var actionContext = new ActionContext(context, routeData, EmptyActionDescriptor); | ||
|
||
return result.ExecuteResultAsync(actionContext); | ||
} | ||
} | ||
} | ||
#endif |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.