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);
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.
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);
}
Related
I am developing a rest application.
Some endpoints require a custom header parameter, not related to authorisation. I created a custom annotation using jax-rs NameBinding. Here is an usage example:
#GET
#RequiresBankHeader
public int get(
#HeaderParam("bank")
#Parameter(ref = "#/components/parameters/banks")
String bank) {
return someService.getSomeInformation();
}
There is a provider that intercepts this call and do some routine using the information in the header parameter.
The problem is that I have to repeat '#HeaderParam("bank") #Parameter(ref = "#/components/parameters/banks") String bank' everywhere, just so it appears in Swagger, even though the service classes do not need it. I was able to at least reuse the parameter definition with ref = "#/components/parameters/banks", and declaring it in the OpenAPI.yml file, that Quarkus merges with generated code very nicely.
But I also want to create and interceptor to dynamically add this do the OpenApi definition whenever RequiresBankHeader annotation is present.
Is there a way to do it?
I dont think you can use interceptors to modify the generated Openapi schema output.
If all methods on a given endpoint require some parameter, you can specify it on class level like so:
#Path("/someendpoint")
public class MyEndpoint {
#HeaderParam("bank")
#Parameter(name = "bank")
String bank;
#GET
public Response getAll() {return Response.ok().build()}
#GET
#Path("{id}")
public Response someMethod(#PathParam("id") String id) {return Response.ok().build();}
}
As mentioned by Roberto Cortez, the MP OpenAPI spec provides a programmatic way to contribute metadata to the openapi.yml file.
It is not possible to detect an annotation in the JAX-RS endpoint definition, but it was good enough to automate what I needed. Since all methods that had the RequiresBankHeader return the same Schema, I was able to hack it like this:
public class OpenApiConfigurator implements OASFilter {
#Override
public Operation filterOperation(Operation operation) {
operation.getResponses().getAPIResponses().values().stream().
map(APIResponse::getContent).
filter(Objects::nonNull).
map(Content::getMediaTypes).
flatMap(mediaTypes -> mediaTypes.values().stream()).
map(MediaType::getSchema).
filter(Objects::nonNull).
map(Schema::getRef).
filter(Objects::nonNull).
filter(ref -> ref.contains("the common response schema")).
findAny().
ifPresent(schema -> {
ParameterImpl parameter = new ParameterImpl();
parameter.setRef("#/components/parameters/banks");
operation.addParameter(parameter);
});
return operation;
}
OpenApiConfigurator should be configure in the application properties, using mp.openapi.filter=com.yourcompany.OpenApiConfigurator
I'm having problems defining a function for odata4. The default get would work but I want to require a user parameter so a client set can be determined, other tables are involved so LINQ is required, I also return a DTO instead of the default table info (EF). Below is the code. I get a "Invalid EntitySetPath detected. 'bindingParameter/Client' is not a valid entity set path for procedure 'Default.GetClients'." What am I doing wrong here?
WebApiConfig
public static void Register(HttpConfiguration config)
{
ODataModelBuilder builder = new ODataConventionModelBuilder();
builder.EntitySet<client>("Client").EntityType.HasKey(p => p.int_id);
var function = builder.Function("GetClients");
function.Parameter<string>("user");
function.ReturnsCollectionFromEntitySet<client>("Client");
builder.EntitySet<ClientDTO>("ClientDTO");
config.MapODataServiceRoute(
routeName: "ODataRoute",
routePrefix: null,
model: builder.GetEdmModel());
WebApp.Controller
[ODataRoute("GetClients(user={user})")]
[EnableQuery(PageSize=25)]
public IQueryable<ClientDTO> GetClients([FromODataUri] string user)
{
var clients = (from c in db.clients
join ...
If your OData controller is returning the DTO, the function should look like this:
var function = builder.Function("GetClients");
function.Parameter<string>("user");
function.ReturnsCollectionFromEntitySet<ClientDTO>("Client");
With your current setup, your OData route of GetClients says that it is returning a ClientDTO object, but your WebApiConfig is stating you are returning a Client object.
As the Entity Collection being returned is actually the DTO. The part that shows ("Client") is simply how the OData service will report the name of the object to the project consuming the OData service. For my own personal sanity, I typically include DTO as well so I know when I'm using a DTO and when I'm using a direct entity. So in my own setup i'd return ("ClientDTO"), just a personal preference.
I'm in the process of making a RESTful client (consumption side) and so far I have all my requests and responses fairly consistent. I have something like this:
public class RESTClient
{
public T Execute<T>(RESTRequest request);
}
My RESTRequest contains things like the base URI, authentication, resource URI and any object(s) to serialize. So far so good, until I came across a situation where the result can't be deserialized to a concrete type. This result is a collection of, for example, SomeClass with (at the same level) the number of results in the collection. So let's say SomeClass looks like this:
public class SomeClass
{
public int Id { get; set; }
public string Name { get; set; }
}
If I request the specific resource, then it deserializes perfectly fine - but I can't do that unless I have the ID, so for that I need to get it from a list that when requested returns JSON that looks like this:
{
"-1":
{
"id": -1,
"name": "There is *always* a -1 in the collection as default"
},
"3":
{
"id": 3,
"name": "This would be one I've added later"
},
"recordsCount": 2
}
So as you can see the ID of the object is the name of the object... and at the same level we have recordsCount which is just an int.
First Attempt
My first attempt was (as I'm working with JSON.Net) was to go through the result as JObject first, check if the name of the current object matched my selector and then do another request for SomeClass by ID to get the nicely deserialized concrete class. Basically trawling through the JSON until I find something I can deserialize to my target class.
Second Attempt
After refactoring the first attempt (and decoupling my serializer from JSON.Net) I decided to pass in a custom serializer (say SomeClassSerializer) via the RESTRequest itself, so in most cases I can use the basic JSON <--> Concrete version, and when I need to I can override it with this specific one.
My actual questions are:
Am I alone in thinking this particular JSON response is just a horrible mess? What were they thinking?!
Is there a better way of handling this (either in JSON.Net or not)... because the names of the objects are integers (and just so happen to be the ID) I can't exactly return them as IEnumerable<TWeirdType>.
Cheers for any help.
It was a lot simpler (and more obvious) than I initially thought... I wanted a solution that would keep my RESTClient and JSON.Net loosely coupled, and I can do just that with JsonConverter (which is part of JSON.Net but the logic is outside of my client and all happens through type).
What I've got instead is:
public interface IMySerializer
{
string Serialize<T>(T target);
T Deserialize<T>(string json);
}
Which is used by my RESTClient with a concrete type of BasicJsonSerializer (which has JSON.Net's serializer inside it) and this custom converter (passed through JsonSerializerSettings).
public IEnumerableSomeClassConverter : JsonConverter
{
public override bool CanConvert(Type objectType) {}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
// Handle the weirdness here and return my IEnumerable
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) {}
}
Handling it very well.
I'm starting to learn Json.NET, but I'm having trouble using its serializer. I have a new MVC4 project with a Web.API service:
public class PTE_TestsController : ApiController {
PTE_TestsRepository _repository = new PTE_TestsRepository();
// GET /api/PTE_Tests/5
public HttpResponseMessage<string> Get(int id) {
try {
PTE_Test test = _repository.GetTest(id);
return new HttpResponseMessage<string>(JsonConvert.SerializeObject(test));
} catch {
return new HttpResponseMessage<string>(HttpStatusCode.NotFound);
}
}
}
JsonConvert.SerializeObject() works as expected and returns a string. My Web.API controller returns that as part of an HttpResponseMessage. The end result, when viewed in Fiddler, is not JSON data, but JSON data being serialized again (I think):
"{\"ID\":1,\"Name\":\"Talar Tilt\",\"TagID\":1,\"PracticeID\":1,
\"SpecificAreaID\":1,\"TypeID\":1,\"VideoID\":1,\"PicID\":1}"
Does someone know how to turn off the default serializer so I can use Json.NET directly? By the way, I'm not using the default serializer because I can't figure out how to make it work with complex objects (PTE_Test will eventually contain members of type List).
Alternately, it will also solve my problem if someone can explain how to use the default serializer with complex objects. MSDN's explanation didn't help me.
Rick Strahl has a blog on that here with a code that works.
As others have pointed out, you need to create a formatter and replace the DataContractSerializer with the JSON.NET serializer. Although, if you're not in a rush for JSON.NET specifically, rumor has it that next beta/rc is going to have support for JSON.NET built in.
Conceptually, however, you're missing part of the magic of WebAPI. With WebAPI you return your object in it's native state (or IQueryable if you want OData support). After your function call finishes the Formatter's take over and convert it into the proper shape based on the client request.
So in your original code, you converted PTE_Test into a JSON string and returned it, at which point the JSON Formatter kicked in and serialized the string. I modified your code as follows:
public class PTE_TestsController : ApiController {
PTE_TestsRepository _repository = new PTE_TestsRepository();
public HttpResponseMessage<PTE_Test> Get(int id) {
try {
PTE_Test test = _repository.GetTest(id);
return new HttpResponseMessage(test);
} catch {
return new HttpResponseMessage<string>(HttpStatusCode.NotFound);
}
}
}
Notice how your function returns PTE_Test instead of string. Assuming the request came in with a request header of Accept = application/json then the JSON formatter will be invoked. If the request had a header of : Accept = text/xml the XML formatter is invoked.
There's a decent article on the topic here. If you're a visual learner, Scott Gu shows some examples using fiddler in this video, starting around 37 minutes. Pedro Reys digs a little deeper into content negotiation here.
The way to do this is to use formatters.
Check out: https://github.com/WebApiContrib/WebAPIContrib/tree/master/src/WebApiContrib.Formatting.JsonNet.
Json.NET support will be in the RC release of Web API.
Hi I'm hitting my head against the wall here...
I'm using DataContractJsonSerializer to encode data that I'm retrieving from the database which I'm sending back to an AJAX call...
I have this extension:
public static string ToJSON<T>(this T obj) where T : class
{
DataContractJsonSerializer serializer = new DataContractJsonSerializer(typeof(T));
using (MemoryStream stream = new MemoryStream())
{
serializer.WriteObject(stream, obj);
return Encoding.Default.GetString(stream.ToArray());
}
}
and I'm calling it from the ASMX:
return _webServiceService.GetSponsors().ToJSON();
Passing in the resultset from a Stored Procedure using Entity Framework
It's all work kinda working, except this is part of what's being returned:
{"d":"[{\"FileID\":296,\"IconUrl\":\"\\/Files\\/Banners\\/20119\\/00B56BAC.jpg\",\"ImageUrl\":....
How on earth to I get rid of the slashes so that it returns the following:
{"d":"[{"FileID":296,"IconUrl":"/Files/Banners/20119/00B56BAC.jpg","ImageUrl":....
I'm trying to implement the JQuery UI Autocomplete. Well I'm guessing this will sort it, not sure if I need the FileID value within quotes?
You don't need to JSON serialize the return value by hand, because ASP.NET will handle it for you automatically in that scenario. What you're seeing there is ASP.NET applying a second level of JSON serialization to your manually generated string, which requires escaping double quotes with backslashes.
Change your ASMX method to return something like List<Sponsor> and return _webServiceService.GetSponsors() directly, without the ToJSON() extension.