Comments (5)
This is a limitation of ASP.NET Core Model Binding, not an issue of Optional<T>
. You need to have your own Type Converter for Optional<T>
as described here.
from dotnext.
Moreover, the binding architecture of ASP.NET Core passes empty string to TryParse
for the type with custom binding. In case of Optional<string>
it means that you'll get non-empty Optional container with empty string instead of None
.
from dotnext.
According to docs, TryParse
is the only way of binding for non-complex types. I see no benefits of Optional<T>
for query parameters. Even nullable string is redundant because ASP.NET Core never passes null to the parameter.
from dotnext.
Thanks for the help. Good point that string?
can't be null if the parameter is included in the query string. I ended up going with the solution below. It works well enough for my case. Hopefully someone else will find it useful too if they need optional query parameters. Sadly it doesn't make use of the Optional<T>
struct though.
public readonly struct OptionalString(string? value)
{
public static OptionalString Undefined => new()
{
IsUndefined = true
};
public bool IsUndefined { get; private init; } = false;
public string? Value { get; } = value;
}
public class OptionalStringModelBinder : IModelBinder
{
public Task BindModelAsync(ModelBindingContext bindingContext)
{
var modelName = bindingContext.ModelName;
var valueProviderResult = bindingContext.ValueProvider.GetValue(modelName);
if (valueProviderResult == ValueProviderResult.None)
{
bindingContext.Result = ModelBindingResult.Success(OptionalString.Undefined);
return Task.CompletedTask;
}
if (valueProviderResult.Values.Count > 1)
{
bindingContext.ModelState.TryAddModelError(modelName, "Only a single value is allowed.");
bindingContext.Result = ModelBindingResult.Failed();
return Task.CompletedTask;
}
OptionalString optionalString;
if (string.IsNullOrEmpty(valueProviderResult.FirstValue))
{
// As pointed out, the value will never actually be null here so an empty string will have to suffice.
// I'm not sure how I'd handle a scenario where the string could be undefined, null, empty, whitespace, or non-empty and non-whitespace.
// I think it would require binding an additional parameter value.
optionalString = new OptionalString(null);
}
else
{
optionalString = new OptionalString(valueProviderResult.FirstValue);
}
bindingContext.Result = ModelBindingResult.Success(optionalString);
return Task.CompletedTask;
}
}
public class QueryParameters
{
// Could of course be registered globally via a ModelBinderProvider instead of an attribute.
[ModelBinder(BinderType = typeof(OptionalStringModelBinder))]
public OptionalString SomeString { get; init; }
}
[ApiController]
public class QueryController : ControllerBase
{
[HttpGet("query")]
public IActionResult Query([FromQuery] QueryParameters parameters)
{
// Do stuff here
}
}
GET /query
: Undefined
GET /query?SomeString
: null
GET /query?SomeString=value
: "value"
I have another model binder that I'm using for long?
s as well.
// This is identical to OptionalString except for the value type.
// It's a shame generics don't work with model binding, but if they did I could just use Optional<T> instead of making these knockoffs.
public readonly struct OptionalLong(long? value)
{
public static OptionalLong Undefined => new()
{
IsUndefined = true
};
public bool IsUndefined { get; private init; } = false;
public long? Value { get; } = value;
}
public class OptionalLongModelBinder : IModelBinder
{
public Task BindModelAsync(ModelBindingContext bindingContext)
{
var modelName = bindingContext.ModelName;
var valueProviderResult = bindingContext.ValueProvider.GetValue(modelName);
if (valueProviderResult == ValueProviderResult.None)
{
bindingContext.Result = ModelBindingResult.Success(OptionalLong.Undefined);
return Task.CompletedTask;
}
if (valueProviderResult.Values.Count > 1)
{
bindingContext.ModelState.TryAddModelError(modelName, "Only a single value is allowed.");
bindingContext.Result = ModelBindingResult.Failed();
return Task.CompletedTask;
}
var stringValue = valueProviderResult.FirstValue;
if (string.IsNullOrWhiteSpace(stringValue))
{
bindingContext.Result = ModelBindingResult.Success(new OptionalLong(null));
}
else if (long.TryParse(stringValue, out var value))
{
bindingContext.Result = ModelBindingResult.Success(new OptionalLong(value));
}
else
{
bindingContext.ModelState.TryAddModelError(modelName, "Value must be null or a long.");
bindingContext.Result = ModelBindingResult.Failed();
}
return Task.CompletedTask;
}
}
from dotnext.
Agreed regarding your comment about TryParse
not being helpful for this situation. I thought I'd be able to use an IModelBinder
but I'm still getting the same error:
public class OptionalStringBinder : IModelBinder
{
public Task BindModelAsync(ModelBindingContext bindingContext)
{
var modelName = bindingContext.ModelName;
var valueProviderResult = bindingContext.ValueProvider.GetValue(modelName);
if (valueProviderResult == ValueProviderResult.None)
{
bindingContext.Result = ModelBindingResult.Success(Optional.None<string>());
return Task.CompletedTask;
}
var value = valueProviderResult.FirstValue;
bindingContext.Result = ModelBindingResult.Success(new Optional<string>(value));
return Task.CompletedTask;
}
}
Is this something worth asking about on the aspnetcore project, or would you advise that I go back to using nullable strings for my scenario?
from dotnext.
Related Issues (20)
- Update to .NET 8 and AOT compatibility
- Raft Cluster Asp.net Core Dynamically AddMemberAsync HOT 5
- [API Proposal]: Add .ctor(Span<T> span) to MemoryOwner<T> HOT 10
- API Ref: Universally Broken Links (Wrong Repo Target) HOT 4
- ASP.net Cluster Raft question to configure full in memory mode HOT 1
- Quorum node for a 2-node cluster
- Using Optional<T> with data validation attributes HOT 5
- `AsyncBarrier.AddParticipant()` does not work
- Cluster node status HOT 1
- DotNext vs Community toolkit
- Support for non-LTS .NET releases HOT 1
- Various trimming warnings in DotNext.Metaprogramming HOT 15
- Potential addition of an `OrderedDictionary<TKey, TValue>` type HOT 6
- raft leader loses leadership and the node gets stuck HOT 19
- Metaprogramming: Try-catch not catching exception HOT 2
- ask for set up workaround to make AOT works HOT 4
- Directly reference algorithm used in int sqrt HOT 3
- Not seeing expected improvement in throughput of RaftCluster.ReplicateAsync method when cluster minority is inaccessible HOT 20
- Seemingly random NullReferenceException in async state machine HOT 13
Recommend Projects
-
React
A declarative, efficient, and flexible JavaScript library for building user interfaces.
-
Vue.js
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
-
Typescript
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
-
TensorFlow
An Open Source Machine Learning Framework for Everyone
-
Django
The Web framework for perfectionists with deadlines.
-
Laravel
A PHP framework for web artisans
-
D3
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
-
Recommend Topics
-
javascript
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
-
web
Some thing interesting about web. New door for the world.
-
server
A server is a program made to process requests and deliver data to clients.
-
Machine learning
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
-
Visualization
Some thing interesting about visualization, use data art
-
Game
Some thing interesting about game, make everyone happy.
Recommend Org
-
Facebook
We are working to build community through open source technology. NB: members must have two-factor auth.
-
Microsoft
Open source projects and samples from Microsoft.
-
Google
Google ❤️ Open Source for everyone.
-
Alibaba
Alibaba Open Source for everyone
-
D3
Data-Driven Documents codes.
-
Tencent
China tencent open source team.
from dotnext.