Open mathiash98 opened 2 years ago
After fiddling around we first tried out hiding the interface method using the new
keyword:
public interface IDemoApiForClient : IDemoApi
{
[Get("/")]
new Task<object> Get();
}
IActionResults
which still failed, and this will result in lots of definition duplicationSo then we ended up creating a Bash script in the nuget package (for the shared interface) which copied the server interface and removed all ActionResult<T>
and IActionResult
.
The bash script looks something like this:
#!/bin/bash
## Generate Cloud API Client Interface in order for clients to utilize the same interface as server
INTERFACE_NAME="ISharedConfigServerApi"
OUTPUT_NAME="ISharedConfigClientApi"
OUTPUT_FILE_NAME="${OUTPUT_NAME}.cs"
echo "/* AUTO GENERATED FILE. DO NOT EDIT */
$(cat "${INTERFACE_NAME}.cs")" > $OUTPUT_FILE_NAME
# Rename interface
sed -i "s/${INTERFACE_NAME}/${OUTPUT_NAME}/g" $OUTPUT_FILE_NAME
# Update interface <summary>x</summary> comment
sed -i "s/Only to be used on server side/Only to be used on client side/g" $OUTPUT_FILE_NAME
# Rewrite Task<IActionResult> -> Task
sed -i "s/Task<IActionResult>/Task/g" $OUTPUT_FILE_NAME
# Rewrite Task<ActionResult<T>> -> Task<T>
sed -i "s/Task<ActionResult<\([^>]*\)>>/Task<\1>/g" $OUTPUT_FILE_NAME
dotnet restore
:steps:
- task: Bash@3
displayName: "Generate client interface for CloudApi"
inputs:
workingDirectory: "$(Build.SourcesDirectory)/InRange.Config.Dto/CloudAPI/"
filePath: "$(Build.SourcesDirectory)/InRange.Config.Dto/CloudAPI/GenerateCloudApiClientInterface.sh"
failOnStderr: true
- task: DotNetCoreCLI@2
displayName: "Restore"
condition: succeeded()
inputs:
command: restore
"...it is impossible to deserialize a json coming from an interface using ActionResult
"I realize that ActionResult is not meant for the client side, but to truly share interface across server and client it is necessary to include it in the interface file."
You're running into a problem here because it appears you're attempting to do something that you're really not supposed to. ActionResult
is a concern only for MVC and it doesn't really actually form part of the interface between the client and server despite being in the return type on the controller actions. If you return ActionResult<Foo>
then Foo
serialized as json is what goes over the wire and that's effectively the interface. So the corresponding Refit interface on the client side simply needs a return type of Task<ApiResponse<Foo>>
and everything works perfectly. Attempting to leak specific MVC implementation details across that boundary isn't appropriate and that's why you're running into problems attempting to do it.
Modern .NET APIs have great integration with swagger / open-api and can automatically generate the API schema based from the controller actions if you annotate them correctly as well. You can have the return type as IActionResult
and use the [ProducesResponseType]
attribute to tell swagger/open-api what the API interface actually is. See the docs: https://docs.microsoft.com/en-us/aspnet/core/web-api/action-return-types?view=aspnetcore-6.0#synchronous-action
ActionResult vs IActionResult
Describe the bug
The benefit of using Refit is the ability to share API interface across applications. But due to limitations in both Text.Json and Newtonsoft.Json it is impossible to deserialize a json coming from an interface using
ActionResult<object>
as return type becauseActionResult<object>
does not have a parameterless constructor.IActionResult
works fine.I'm not sure if this is possible to solve in Refit as it is due to Text.Json and Newtonsoft.Json limitations. But there should be some guidelines as to what to do as
ActionResult<object>
is a very common return type of API endpoints.Steps To Reproduce
Unit test file
```CSharp using Microsoft.AspNetCore.Mvc; using Microsoft.VisualStudio.TestTools.UnitTesting; using Newtonsoft.Json; using Refit; using System; using System.Threading.Tasks; using FluentAssertions; using WireMock.RequestBuilders; using WireMock.ResponseBuilders; using WireMock.Server; using Newtonsoft.Json.Serialization; namespace RefitActionResult { [TestClass] public class RefitActionResultTest { private WireMockServer _server = null!; [TestInitialize] public void SetUp() { _server = WireMockServer.Start(); } [TestCleanup] public void TearDown() { try { _server.Stop(); } catch (OperationCanceledException) { // OK, we're done anyway. } } [TestMethod] public async Task DeserializeActionResult_Refit_Ok() { var scenarioName = "Deserialize minimal SiteConfigDto"; _server.Given(Request.Create().WithPath("/").UsingGet()) .InScenario(scenarioName) .WillSetStateTo("Sync 1") .RespondWith(Response.Create() .WithStatusCode(200) .WithBody(JsonConvert.SerializeObject(new object() { })) ); var simpleApi = RestService.For*.csproj file. The same error occurs in net6.0 as well
```CSharpExpected behavior When using an interface with
ActionResult
as return type, Refit needs to handle this somehowEnvironment
Please close or move to elsewhere as you see fit