Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

It is not fully compatible with ASP.NET 8 #559

Closed
arteny opened this issue Feb 8, 2024 · 6 comments
Closed

It is not fully compatible with ASP.NET 8 #559

arteny opened this issue Feb 8, 2024 · 6 comments
Labels
enhancement New feature or request

Comments

@arteny
Copy link

arteny commented Feb 8, 2024

Describe the feature

Only case you described in doc is adding full route for each controller's method. But it is not very useful case. Most of cases route generated automatically for common rules for controller and method has only [HttpGet] attribute.
But it is not works in this case.
Also it is not working for minimal API mechanism of Asp.Net
For instance, the following code

app.MapGet("test/{eventId}", (EventId eventId) => Results.Ok(eventId));

generates the error: error ASP0020: Parameter 'eventId' of type EventId should define a bool TryParse(string, IFormatProvider, out EventId) method, or implement IParsable<EventId> (https://aka.ms/aspnet/analyzers)
which is fixing by adding ValueObject's method:

   public static bool TryParse(string value, IFormatProvider provider, out EventId eventId)
   {
   	eventId= From(value);
   	return true;
   }

Could you please to add more compatiblity with Asp.Net?

@arteny arteny added the enhancement New feature or request label Feb 8, 2024
@SteveDunn
Copy link
Owner

SteveDunn commented Apr 19, 2024

Thanks for the bug report! This is fixed in #570 - please let me know how you get on, which will be available in v4.0.0, hopefully later today.

@fbestfriends
Copy link

@SteveDunn it is working now for this case, thanks.
Only what is not working - it is parameters for Azure Functions. If is it possible to fix this, will be great.
Code:

[ValueObject<string>(conversions: Conversions.EfCoreValueConverter | Conversions.Default)]
public partial struct MarketId{}

		[Function("Function1")]
		public async Task<MarketId> Run1([HttpTrigger(AuthorizationLevel.Anonymous, "get", "post")] HttpRequestData req, 
			MarketId marketId)
		{
			return marketId;
		}

Test call: http://localhost:7129/api/Function1?marketId=s

Result:

[2024-04-23T20:10:57.045Z] Executing 'Functions.Function1' (Reason='This function was programmatically called via the host APIs.', Id=84494f7d-8bef-4fcf-ab56-ba059fa68f05)
[2024-04-23T20:10:57.064Z] Function 'Function1', Invocation id '84494f7d-8bef-4fcf-ab56-ba059fa68f05': An exception was thrown by the invocation.
[2024-04-23T20:10:57.064Z] Result: Function 'Function1', Invocation id '84494f7d-8bef-4fcf-ab56-ba059fa68f05': An exception was thrown by the invocation.
Exception: Microsoft.Azure.Functions.Worker.FunctionInputConverterException: Error converting 1 input parameters for Function 'Function1': Cannot convert input parameter 'marketId' to type 'Domain.Enums.MarketId' from type 'System.String'. Error:System.Text.Json.JsonException: 's' is an invalid start of a value. Path: $ | LineNumber: 0 | BytePositionInLine: 0.
[2024-04-23T20:10:57.065Z]  ---> System.Text.Json.JsonReaderException: 's' is an invalid start of a value. LineNumber: 0 | BytePositionInLine: 0.
[2024-04-23T20:10:57.065Z] Executed 'Functions.Function1' (Failed, Id=84494f7d-8bef-4fcf-ab56-ba059fa68f05, Duration=21ms)
[2024-04-23T20:10:57.065Z]    at System.Text.Json.ThrowHelper.ThrowJsonReaderException(Utf8JsonReader& json, ExceptionResource resource, Byte nextByte, ReadOnlySpan`1 bytes)
[2024-04-23T20:10:57.066Z] System.Private.CoreLib: Exception while executing function: Functions.Function1. System.Private.CoreLib: Result: Failure
Exception: Microsoft.Azure.Functions.Worker.FunctionInputConverterException: Error converting 1 input parameters for Function 'Function1': Cannot convert input parameter 'marketId' to type 'Domain.Enums.MarketId' from type 'System.String'. Error:System.Text.Json.JsonException: 's' is an invalid start of a value. Path: $ | LineNumber: 0 | BytePositionInLine: 0.
[2024-04-23T20:10:57.066Z]    at System.Text.Json.Utf8JsonReader.ConsumeValue(Byte marker)
[2024-04-23T20:10:57.067Z]  ---> System.Text.Json.JsonReaderException: 's' is an invalid start of a value. LineNumber: 0 | BytePositionInLine: 0.
[2024-04-23T20:10:57.067Z]    at System.Text.Json.Utf8JsonReader.ReadFirstToken(Byte first)
[2024-04-23T20:10:57.067Z]    at System.Text.Json.ThrowHelper.ThrowJsonReaderException(Utf8JsonReader& json, ExceptionResource resource, Byte nextByte, ReadOnlySpan`1 bytes)
[2024-04-23T20:10:57.068Z]    at System.Text.Json.Utf8JsonReader.ReadSingleSegment()
[2024-04-23T20:10:57.068Z]    at System.Text.Json.Utf8JsonReader.Read()
[2024-04-23T20:10:57.069Z]    at System.Text.Json.Serialization.JsonConverter`1.ReadCore(Utf8JsonReader& reader, JsonSerializerOptions options, ReadStack& state)
[2024-04-23T20:10:57.069Z]    --- End of inner exception stack trace ---
[2024-04-23T20:10:57.069Z]    at System.Text.Json.ThrowHelper.ReThrowWithPath(ReadStack& state, JsonReaderException ex)
[2024-04-23T20:10:57.070Z]    at System.Text.Json.Serialization.JsonConverter`1.ReadCore(Utf8JsonReader& reader, JsonSerializerOptions options, ReadStack& state)
[2024-04-23T20:10:57.070Z]    at System.Text.Json.Serialization.Metadata.JsonTypeInfo`1.ContinueDeserialize(ReadBufferState& bufferState, JsonReaderState& jsonReaderState, ReadStack& readStack)
[2024-04-23T20:10:57.068Z]    at System.Text.Json.Utf8JsonReader.ConsumeValue(Byte marker)
[2024-04-23T20:10:57.071Z]    at System.Text.Json.Utf8JsonReader.ReadFirstToken(Byte first)
[2024-04-23T20:10:57.071Z]    at System.Text.Json.Utf8JsonReader.ReadSingleSegment()
[2024-04-23T20:10:57.071Z]    at System.Text.Json.Utf8JsonReader.Read()
[2024-04-23T20:10:57.070Z]    at System.Text.Json.Serialization.Metadata.JsonTypeInfo`1.DeserializeAsync(Stream utf8Json, CancellationToken cancellationToken)
[2024-04-23T20:10:57.072Z]    at System.Text.Json.Serialization.Metadata.JsonTypeInfo`1.DeserializeAsObjectAsync(Stream utf8Json, CancellationToken cancellationToken)
[2024-04-23T20:10:57.071Z]    at System.Text.Json.Serialization.JsonConverter`1.ReadCore(Utf8JsonReader& reader, JsonSerializerOptions options, ReadStack& state)
[2024-04-23T20:10:57.072Z]    at Microsoft.Azure.Functions.Worker.Converters.JsonPocoConverter.GetConversionResultFromDeserialization(Byte[] bytes, Type type) in D:\a\_work\1\s\src\DotNetWorker.Core\Converters\JsonPocoConverter.cs:line 66
[2024-04-23T20:10:57.073Z]    --- End of inner exception stack trace ---
[2024-04-23T20:10:57.073Z]    at System.Text.Json.ThrowHelper.ReThrowWithPath(ReadStack& state, JsonReaderException ex)
[2024-04-23T20:10:57.073Z]    at System.Text.Json.Serialization.JsonConverter`1.ReadCore(Utf8JsonReader& reader, JsonSerializerOptions options, ReadStack& state)
[2024-04-23T20:10:57.073Z]    at Microsoft.Azure.Functions.Worker.Context.Features.DefaultFunctionInputBindingFeature.BindFunctionInputAsync(FunctionContext context) in D:\a\_work\1\s\src\DotNetWorker.Core\Context\Features\DefaultFunctionInputBindingFeature.cs:line 94
[2024-04-23T20:10:57.074Z]    at Microsoft.Azure.Functions.Worker.Invocation.DefaultFunctionExecutor.ExecuteAsync(FunctionContext context) in D:\a\_work\1\s\src\DotNetWorker.Core\Invocation\DefaultFunctionExecutor.cs:line 46
[2024-04-23T20:10:57.074Z]    at Microsoft.Azure.Functions.Worker.OutputBindings.OutputBindingsMiddleware.Invoke(FunctionContext context, FunctionExecutionDelegate next) in D:\a\_work\1\s\src\DotNetWorker.Core\OutputBindings\OutputBindingsMiddleware.cs:line 13
[2024-04-23T20:10:57.074Z]    at System.Text.Json.Serialization.Metadata.JsonTypeInfo`1.ContinueDeserialize(ReadBufferState& bufferState, JsonReaderState& jsonReaderState, ReadStack& readStack)
[2024-04-23T20:10:57.075Z]    at System.Text.Json.Serialization.Metadata.JsonTypeInfo`1.DeserializeAsync(Stream utf8Json, CancellationToken cancellationToken)
[2024-04-23T20:10:57.075Z]    at System.Text.Json.Serialization.Metadata.JsonTypeInfo`1.DeserializeAsObjectAsync(Stream utf8Json, CancellationToken cancellationToken)
[2024-04-23T20:10:57.076Z]    at Microsoft.Azure.Functions.Worker.Converters.JsonPocoConverter.GetConversionResultFromDeserialization(Byte[] bytes, Type type) in D:\a\_work\1\s\src\DotNetWorker.Core\Converters\JsonPocoConverter.cs:line 66
[2024-04-23T20:10:57.076Z]    at Microsoft.Azure.Functions.Worker.Context.Features.DefaultFunctionInputBindingFeature.BindFunctionInputAsync(FunctionContext context) in D:\a\_work\1\s\src\DotNetWorker.Core\Context\Features\DefaultFunctionInputBindingFeature.cs:line 94
[2024-04-23T20:10:57.076Z]    at Microsoft.Azure.Functions.Worker.Invocation.DefaultFunctionExecutor.ExecuteAsync(FunctionContext context) in D:\a\_work\1\s\src\DotNetWorker.Core\Invocation\DefaultFunctionExecutor.cs:line 46
[2024-04-23T20:10:57.077Z]    at Microsoft.Azure.Functions.Worker.OutputBindings.OutputBindingsMiddleware.Invoke(FunctionContext context, FunctionExecutionDelegate next) in D:\a\_work\1\s\src\DotNetWorker.Core\OutputBindings\OutputBindingsMiddleware.cs:line 13
[2024-04-23T20:10:57.075Z]    at Microsoft.Azure.Functions.Worker.FunctionsApplication.InvokeFunctionAsync(FunctionContext context) in D:\a\_work\1\s\src\DotNetWorker.Core\FunctionsApplication.cs:line 77
Stack:    at Microsoft.Azure.Functions.Worker.Context.Features.DefaultFunctionInputBindingFeature.BindFunctionInputAsync(FunctionContext context) in D:\a\_work\1\s\src\DotNetWorker.Core\Context\Features\DefaultFunctionInputBindingFeature.cs:line 94
[2024-04-23T20:10:57.077Z]    at Microsoft.Azure.Functions.Worker.Invocation.DefaultFunctionExecutor.ExecuteAsync(FunctionContext context) in D:\a\_work\1\s\src\DotNetWorker.Core\Invocation\DefaultFunctionExecutor.cs:line 46
[2024-04-23T20:10:57.077Z]    at Microsoft.Azure.Functions.Worker.FunctionsApplication.InvokeFunctionAsync(FunctionContext context) in D:\a\_work\1\s\src\DotNetWorker.Core\FunctionsApplication.cs:line 77
[2024-04-23T20:10:57.078Z]    at Microsoft.Azure.Functions.Worker.OutputBindings.OutputBindingsMiddleware.Invoke(FunctionContext context, FunctionExecutionDelegate next) in D:\a\_work\1\s\src\DotNetWorker.Core\OutputBindings\OutputBindingsMiddleware.cs:line 13
[2024-04-23T20:10:57.078Z]    at Microsoft.Azure.Functions.Worker.FunctionsApplication.InvokeFunctionAsync(FunctionContext context) in D:\a\_work\1\s\src\DotNetWorker.Core\FunctionsApplication.cs:line 77.
[2024-04-23T20:10:57.078Z]    at Microsoft.Azure.Functions.Worker.Handlers.InvocationHandler.InvokeAsync(InvocationRequest request) in D:\a\_work\1\s\src\DotNetWorker.Grpc\Handlers\InvocationHandler.cs:line 88
Stack:    at Microsoft.Azure.Functions.Worker.Context.Features.DefaultFunctionInputBindingFeature.BindFunctionInputAsync(FunctionContext context) in D:\a\_work\1\s\src\DotNetWorker.Core\Context\Features\DefaultFunctionInputBindingFeature.cs:line 94
[2024-04-23T20:10:57.079Z]    at Microsoft.Azure.Functions.Worker.Invocation.DefaultFunctionExecutor.ExecuteAsync(FunctionContext context) in D:\a\_work\1\s\src\DotNetWorker.Core\Invocation\DefaultFunctionExecutor.cs:line 46
[2024-04-23T20:10:57.079Z]    at Microsoft.Azure.Functions.Worker.OutputBindings.OutputBindingsMiddleware.Invoke(FunctionContext context, FunctionExecutionDelegate next) in D:\a\_work\1\s\src\DotNetWorker.Core\OutputBindings\OutputBindingsMiddleware.cs:line 13
[2024-04-23T20:10:57.080Z]    at Microsoft.Azure.Functions.Worker.FunctionsApplication.InvokeFunctionAsync(FunctionContext context) in D:\a\_work\1\s\src\DotNetWorker.Core\FunctionsApplication.cs:line 77
[2024-04-23T20:10:57.080Z]    at Microsoft.Azure.Functions.Worker.Handlers.InvocationHandler.InvokeAsync(InvocationRequest request) in D:\a\_work\1\s\src\DotNetWorker.Grpc\Handlers\InvocationHandler.cs:line 88.

@SteveDunn
Copy link
Owner

From what I can see, for Azure Function parameters, the value object needs to be decorated with an attribute:

[ValueObject<string>(conversions: Conversions.Default)]
[InputConverter(typeof(NameInputConverter))]
public partial struct Name
{
}

The converter looks like this:

public class NameInputConverter : IInputConverter
{
    public ValueTask<ConversionResult> ConvertAsync(ConverterContext context)
    {
        if (context.TargetType == typeof(Name))
        {
            return ValueTask.FromResult(ConversionResult.Success(Name.From(context.Source as string)));
        }

        return ValueTask.FromResult(ConversionResult.Unhandled());
    }
}

This makes this function work correctly:

    public async Task<HttpResponseData> Run([HttpTrigger(AuthorizationLevel.Anonymous, "get")] HttpRequestData req, Name name)

Vogen could generate this attribute, much like it does for type converters. However, the difficulty with this approach arises when value objects wrap primitives such as DateTime, DateTimeOffset etc. It could use the exact same assumptions that the type converters use for dates and times ('round trip' kind, with invariant culture).

I'll keep this idea in mind though. On one hand, it will be useful for the people who use Vogen's Value Objects as Azure function parameters, but on the other hand, it's a fairly big addition for what I guess is a very small amount of users.

@fbestfriends
Copy link

I see. Anyway thank you for workaround, I'll try to use it.

@arteny
Copy link
Author

arteny commented May 16, 2024

It is still not well working with last version. Following code for asp.net conroller method:

		[HttpGet, Route("{marketId}")]
		public IActionResult CurrentOrders([FromRoute] MarketId marketId)

where

[ValueObject]
public partial class MarketId { } 

works in debug mode, but generate following error when publish:
Failed to update your API in Azure (Status code: BadRequest).

So to fix this i need to return for using base layer int type and later don't forget to convert it to ValueObject when using in EF query:

		[HttpGet, Route("{marketId}")]
		public IActionResult CurrentOrders([FromRoute] int marketId)

@SteveDunn
Copy link
Owner

@arteny - this might be Azure specific. I added a controller to the WebApplicationConsumer project and it worked as expected:

image

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

No branches or pull requests

3 participants