GithubHelp home page GithubHelp logo

Comments (36)

jchannon avatar jchannon commented on August 16, 2024 2

Just refreshing myself with Nancy modelbinding and the very basics of it are

  1. it creates an instance of the type binding to
  2. it deserializes the body and applies that
  3. it then effectively loops over querystring/routedata and matches a property name with a route/qs name
  4. finds out if it can assign that value eg the instance doesnt have a value already
  5. tries to assign the instance property to a value
  6. it determines how to convert the value to the type by ITypeProvider eg/datetime, numeric with CanHandle/Handle methods

from carter.

snorith avatar snorith commented on August 16, 2024 1

@abazanov The retrieval for query variables isn't part of routeData, it's part of the request object.

request.Query.TryGetValue("q", out var qVariableStr)

Where qVariableStr is a variable that is the output of the method. The result is always a string, it could be the default value of a string if the variable isn't defined on the URL so you have to check for that and then parse the string value to get your int value.

from carter.

jchannon avatar jchannon commented on August 16, 2024 1

Go for it!

from carter.

jchannon avatar jchannon commented on August 16, 2024

Yeah I was thinking about that the other day. I'm not sure how I feel about it. I never used it in Nancy and not sure of the usage stats on it. I guess we'll leave the issue open and see what people want.

from carter.

jussimattila avatar jussimattila commented on August 16, 2024

My use case is binding requests to query and command objects, which include the route data. For example, route POST "{bundleId:int}/entries/{entryId:int}" could be bound to

public class AddEntryToBundleCommand
{
    public int BundleId { get; set; }
    public int EntryId { get; set; }
}

I find this extremely convenient and I'm actually already using this approach on top of Botwin with some custom code. I'm currently spiking a microservices library for internal use that would allow us to create new Botwin-based services simply by defining routes, actions and handlers for the actions. Just as an example, this is what my "ItemsService" route config looks like:

public class Routes : RoutesBase
{
    public Routes()
    {
        Get("/items", Handle<ListItemsQuery>);
        Post("/items", Handle<CreateItemCommand>);
        Get("/items/{id:int}", Handle<ReadItemQuery>);
        Put("/items", Handle<UpdateItemCommand>);
        Delete("/items/{id:int}", Handle<DeleteItemCommand>);
    }
}

This requires binding of route data, but as I said, I have implemented it in my library code. So if you and other people don't feel the need for binding of route data, no problem.

By the way, I noticed that validation isn't available separate from model binding. That is, there is no Validate(model) method available. If route data model binding is not implemented, refactoring of model validation to its own method would simplify things on my end. And before you ask, yes, I could do a PR for the validation method refactoring if you want.

from carter.

JoeStead avatar JoeStead commented on August 16, 2024

I think if Botwin were to bind to route data, it shouldn't be the same method as binding from the Body, I've seen this confuse people in the past, and it introduces a plethora of questions: body + querystring contain the same property, which should take precedence is the obvious example).

I'm not completely against the idea, especially when it comes to complex query strings (reporting examples are ripe for this), but for day-to-day use, it shouldn't be necessary, so would be reluctant to have it there by default.

from carter.

jchannon avatar jchannon commented on August 16, 2024

I think the Validate separate method is a good idea. Would you like to submit a PR for that, should be a case of copying and pasting half the BindAndValidate method and putting it in a new method.

For model binding route data and querystring I can see the merit I think however the code could be quite nasty. I can take a look and maybe others will jump and also feel free to take a look but it's not my priority tbh

from carter.

jchannon avatar jchannon commented on August 16, 2024

Actually you can already get the route data out like so

this.Get("/actors/{id:int}", async (req, res, routeData) =>
{
    var person = actorProvider.Get(routeData.As<int>("id"));
    await res.Negotiate(person);
});

from carter.

jussimattila avatar jussimattila commented on August 16, 2024

Sure, I'll make a separate issue for the validation stuff and a PR for it tomorrow, if not tonight. I did know how to get the route data but as I want it bound in the model class automatically, I need to iterate available values and bind them to properties or fields using reflection.

from carter.

jchannon avatar jchannon commented on August 16, 2024

from carter.

jchannon avatar jchannon commented on August 16, 2024

Looking at MVC, it seems to bind querystring values to types too so might want to take a look at what they do for that

from carter.

jchannon avatar jchannon commented on August 16, 2024

At the moment I think maybe having a BindRouteData<Foo> separate method might be better but that might change

from carter.

JoeStead avatar JoeStead commented on August 16, 2024

I created #77 to track the bind and validate issue

from carter.

jussimattila avatar jussimattila commented on August 16, 2024

Thanks! I'll have a look at the PR soon.

Regarding your comment about reflection, I'm trying things out for the base library I mentioned and currently doing this to naively bind route data to arbitrary model classes:

var actionType = typeof(T);
var action = request.Bind<T>() ?? Activator.CreateInstance<T>();

foreach (var value in routeData.Values)
{
   var property = actionType.GetProperty(
        value.Key,
        BindingFlags.IgnoreCase | BindingFlags.Public | BindingFlags.Instance);
       var typeConverter = TypeDescriptor.GetConverter(property.PropertyType);
    property.SetValue(action, typeConverter.ConvertFromString(value.Value.ToString()));
}

If you can suggest something better and/or more robust, I wouldn't mind πŸ˜ƒ.

from carter.

jussimattila avatar jussimattila commented on August 16, 2024

Oh, and a separate BindRouteData<Foo> would be great for my purposes!

from carter.

jchannon avatar jchannon commented on August 16, 2024

That's pretty much all you can do I think πŸ‘ but I'm sure we can improve it

from carter.

RagingKore avatar RagingKore commented on August 16, 2024

@jchannon regarding 4, the way you explained means that the body would always win correct? I always get the body data and override it with route data.

Maybe making the Bind priority configurable would please everyone.

from carter.

ravensorb avatar ravensorb commented on August 16, 2024

Any update to this? I am looking for QS model binding and was deciding on if I should start working on something or not.

from carter.

jchannon avatar jchannon commented on August 16, 2024

from carter.

ravensorb avatar ravensorb commented on August 16, 2024

Ok, I finished a version last night that supports basic models (flat with no collections) from the Query String (not the URL/Route though). If all goes well I should be able to get collections working today or this weekend. Question, do we think we need json notation support for more complex objects right now?

from carter.

jchannon avatar jchannon commented on August 16, 2024

Not sure where JSON comes in for querystring/route data?

from carter.

ravensorb avatar ravensorb commented on August 16, 2024

It seems that a lot of newer API use the [] notation to work with nested objects in a query string

Ex:

/someroute/?name=sample&price[gte]=10&price[lte]=100

would parse to

public class QueryStringModel
{
    public string Name {get;set;}
    public PriceModel Price {get;set;}
}

public class PriceModel 
{
    public int Gte {get;set;}
    public int Lte {get;set;}
}

Here is a quick example library from node: https://canjs.com/doc/can-param.html

from carter.

jchannon avatar jchannon commented on August 16, 2024

Personally I've never used it. I think get basic binding working then we can look at the more complicated approach however I'd like it to work off a defined standard if there is one for this type of querystring format

from carter.

ravensorb avatar ravensorb commented on August 16, 2024

Ok, simple solution is as follows :)

public static T BindQueryString<T>(this HttpRequest request, Newtonsoft.Json.JsonSerializer jsonSerializer = null)
{
	var dct = request.Query.ToDictionary(key => key.Key, value => string.Join(",", value.Value.ToArray()));

	JObject jObject;

	if (jsonSerializer != null)
	{
		jObject = Newtonsoft.Json.Linq.JObject.FromObject(dct, jsonSerializer);
	}
	else
	{
		jObject = Newtonsoft.Json.Linq.JObject.FromObject(dct);
	}

	var result = jObject.ToObject<T>();

	return result;
}

If we want to support array values we also will need a custom JsonConverter which is pretty easy to create.

from carter.

jchannon avatar jchannon commented on August 16, 2024

from carter.

ravensorb avatar ravensorb commented on August 16, 2024

Yep,I have a fork with the changes. As for the serializer, I am ok using the class wide one if we can remove the readonly as I find that I need to set it with custom settings a lot. Are you ok with that?

And for the arrays, I can do one of two things -- add a reference to another library that already has the converter or copy the code (its my code so I am ok with either).

https://github.com/Invisionware/Invisionware.Serialization/blob/master/src/Invisionware.Serialization.Json.NET/Converters/NewtownSoftJsonArrayConverter.cs

Thoughts?

from carter.

jchannon avatar jchannon commented on August 16, 2024

I'd like to keep it class wide but then in time add options for serialization into CarterOptions.

I'd like to take a copy of the converter into Carter if that's the only option :)

from carter.

snorith avatar snorith commented on August 16, 2024

It would be awesome to support enums in the model binding.

Something like:

                    if (propertyType.IsEnum)
                    {
                        if (Enum.IsDefined(propertyType, int.Parse(val.Value[0])))
                        {
                            return Enum.Parse(propertyType, val.Value[0], false);
                        }
                        return null;
                    }

though that will only work for enums defined as int

from carter.

abazanov avatar abazanov commented on August 16, 2024

Hi Guys,

Sorry to barge in. I have a find type URL that looks like so companies/find?q=123. Are we saying that Carter does not support query string type variables?

I know I can use it like so companies/find/{q} but what if q is not provided, meaning "just give me everything"? It does not hit the correct rout.

I hope somebody has some input about this.


EDIT
Seems, I can use companies/find/q={q} which works, but again, if q is empty then it does not match the route. Trying to see if q can be made nullable or optional.

from carter.

JoeStead avatar JoeStead commented on August 16, 2024

@abazanov you can read the query string parameters of the request, the route will be companies/find, and we have extension methods to help read the query string parameters. I don't have an editor to hand right now, but explore context.Request

from carter.

abazanov avatar abazanov commented on August 16, 2024

@snorith @JoeStead thank you for coming back to me. For some reason just skips over my route when I use companies/find when I use companies/find/test it works. Strange.

Again, thanks guys. Great help. I think I am close now.

from carter.

jchannon avatar jchannon commented on August 16, 2024

from carter.

abazanov avatar abazanov commented on August 16, 2024

Thanks guys. I appreciate it. I got it working now. And the optional value is a nice thing. Thanks @jchannon

from carter.

Deilan avatar Deilan commented on August 16, 2024

To read querystring values you can use Carter's extension methods to do var q = context.Request.Query.As("q", "my_optional_default_value");

This is worthy of being documented in the readme.

from carter.

bugproof avatar bugproof commented on August 16, 2024

I think it's important feature for people who'd like to port code from controllers to carter easily.
With MVC controllers you can bind to your custom classes easily from many different sources.
image

e.g. I used https://github.com/Biarity/Sieve and it provides SieveModel class which you should bind with [FromQuery]. How could I use SieveModel with Carter?

The bind API is very limited right now as it only supports binding from body.

from carter.

jchannon avatar jchannon commented on August 16, 2024

I believe this is now kind of available in .NET6 and Carter 6

app.MapGet("/bind/{id:int}", (HttpContext context, int id) => { });

from carter.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    πŸ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo 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.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❀️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.