1

I know that in ASP.NET Core it is possible to automatically convert some query string to an object using special model binding attributes - [FromRoute], [FromQuery] and so on.

Is it possible to perform such a conversion manually? Something like:

var queryString = Request.QueryString;
var obj = SomeMagic.QueryStringToObject<MyClass>(queryString);

2 Answers 2

1

You can use QueryHelpers.ParseQuery(String) to parse a query string into a dictionary.

If you want the actual same behavior as provided by the [FromQuery] attribute I'd look at the QueryParameterValueSupplier.RenderParametersFromQueryString method, which does most of the heavy-lifting. I'm not sure if this is meant to be used outside of the existing ASP.NET Core framework infrastructure.

Note that a query string is just a collection of string-based name-value pairs. There's no standard that dictates how this should be mapped to something more complex like a Java or C# class. So frameworks like ASP.NET Core build their own convention on top of that, in order to make their complex binding mechanisms work. (e.g. foo.bar[2]=123). ASP.NET Core actually has two ways of binding query strings to a model (the "MVC" way and the "jQuery" way), see JQueryKeyValuePairNormalizer.

    // This is a helper method for Model Binding over a JQuery syntax.
    // Normalize from JQuery to MVC keys. The model binding infrastructure uses MVC keys.
    // x[] --> x
    // [] --> ""
    // x[12] --> x[12]
    // x[field]  --> x.field, where field is not a number
    private static string NormalizeJQueryToMvc(StringBuilder builder, string key)

Finally on a personal note I tend to avoid the query string for anything more complex than simple name-value pairs. When you start to pull in more complex data structures you also run into many limitations. For instance: differentiating between null and empty strings; awkward syntax for handling collections; etc. If I really must use the query string for passing along complex data structures, I fallback to a single Base64 encoded JSON-string and handle that manually within my code.

Sign up to request clarification or add additional context in comments.

5 Comments

Of course, but this approach works with simple query strings only (eg. firstName=John&lastName=Doe) and does not support complex object model binding (firstName=John&lastName=Doe&address.country=USA&address.city=Boston&phone[0]=5555555555).
Ah gotcha, I'll edit the answer. I'm not sure if there's a utility method or class you can use to give you the same behavior.. but maybe you can directly call QueryParameterValueSupplier.RenderParametersFromQueryString which is the same mechanism used internally by ASP.NET Core to handle [FromQuery] attributes.
Thanks a lot! Actually I'm also looking for a possibility to reuse ASP.NET Core model binder, but don't know how to do that. In ASP.NET MVC 5 there was a class named DefaultModelBinder but there is no analog in ASP.NET Core.
You're right I think if you want to do this manually you'd need both the mechanism for parsing the query string and a mechanism from projecting those key-value pairs to actual C# types (including complex types like custom model classes). I see ASP.NET Core has a DefaultModelBindingContext.CreateBindingContext github.com/dotnet/aspnetcore/blob/…
Wow! I was managed to get an instance of IModelBinderFactory from services collection but stuck on a task to initialize and properly populate IModelBinder context. And I just didn't notice that static method!
1

Finally I found more generic solution than just parsing a query string. Here I get an instance of IModelBinder (actually an instance of ComplexObjectModelBinder) and use that as a service.

// DTO
//
public class PersonName
{
    public string FirstName { get;set; }
    public string LastName { get;set; }
}



// Action handler
// Here I want to convert HTTP request to an instance of PersonName manually
// Example: /SearchByName?LastName=Doe&FirstName=John
//
[AcceptVerbs("GET")]
public async Task<IActionResult> SearchByName(
  [FromServices] IModelMetadataProvider modelMetadataProvider,
  [FromServices] IModelBinderFactory modelBinderFactory)
{
    var valueProvider = await CompositeValueProvider.CreateAsync(ControllerContext);
    var modelMetadata = modelMetadataProvider.GetMetadataForType(typeof(PersonName));
    var modelBinderFactoryContext = new ModelBinderFactoryContext
    {
        Metadata = modelMetadata,
        CacheToken = modelMetadata
    };
    var modelBinder = modelBinderFactory.CreateBinder(modelBinderFactoryContext);
    var modelBindingContext= DefaultModelBindingContext.CreateBindingContext(ControllerContext, valueProvider, modelMetadata, new BindingInfo(), string.Empty);
    await modelBinder.BindModelAsync(modelBindingContext);
    var personName = modelBindingContext.Model as PersonName;
    // ...
    return Ok(personName);
}

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.