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.
Related
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);
}
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've been using the MVC4 beta and am currently working to upgrade to the recently released RC version.
It appears that model-binding complex request types has changed, but I can't figure out how / what I'm doing wrong.
For example, say I have the following API controller:
public class HomeApiController : ApiController
{
public TestModel Get()
{
return new TestModel
{
Id = int.MaxValue,
Description = "TestDescription",
Time = DateTime.Now
};
}
}
This yields the expected result:
<TestModel xmlns:i="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://schemas.datacontract.org/2004/07/xxxx">
<Description>TestDescription</Description>
<Id>2147483647</Id>
<Time>2012-06-07T10:30:01.459147-04:00</Time>
</TestModel>
Now say I just change the signature, taking in a request type, like this:
public TestModel Get(TestRequestModel request)
{
...
public class TestRequestModel
{
public int? SomeParameter { get; set; }
}
I now get the following error:
<Exception xmlns:i="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://schemas.datacontract.org/2004/07/System.Web.Http.Dispatcher">
<ExceptionType>System.InvalidOperationException</ExceptionType>
<Message>
No MediaTypeFormatter is available to read an object of type 'TestRequestModel' from content with media type ''undefined''.
</Message>
<StackTrace>
at System.Net.Http.HttpContentExtensions.ReadAsAsync[T](HttpContent content, Type type, IEnumerable`1 formatters, IFormatterLogger formatterLogger) at System.Net.Http.HttpContentExtensions.ReadAsAsync(HttpContent content, Type type, IEnumerable`1 formatters, IFormatterLogger formatterLogger) at System.Web.Http.ModelBinding.FormatterParameterBinding.ExecuteBindingAsync(ModelMetadataProvider metadataProvider, HttpActionContext actionContext, CancellationToken cancellationToken) at System.Web.Http.Controllers.HttpActionBinding.<>c__DisplayClass1.<ExecuteBindingAsync>b__0(HttpParameterBinding parameterBinder) at System.Linq.Enumerable.WhereSelectArrayIterator`2.MoveNext() at System.Threading.Tasks.TaskHelpers.IterateImpl(IEnumerator`1 enumerator, CancellationToken cancellationToken)
</StackTrace>
</Exception>
I've looked at the source code of where this exception is thrown in the HttpContentExtensions, but it looks like it checks for content headers (which I should have), and if it doesn't have that it tries to get a formatter from the MediaTypeFormatter collection it has for the specific type (which it can't) and then throws.
Anyone else experienced this? Some global registration I'm missing?
I see your original question was answered, but to answer the other one, Model binding has changed somewhat in the RC.
http://weblogs.thinktecture.com/cweyer/2012/06/aspnet-web-api-changes-from-beta-to-rc.html
This link has some details about it. But to sum up the change that appears to be affecting you, Model binding pulls its values from either the body, or the uri of the request. This is true for previous releases as well, but with the release candidate, MVC4 will, by default, look to the body for complex types, and the uri for value types.
So, if you submit a body with your request containing the "SomeParameter" key, you should see it bind. Or you could bind with the url if you change the declaration to:
public TestModel Get(int? someParameter)
{
}
Thankfully, the team foresaw the potential problems with this and left us with attributes we could use to override this behavior.
public TestModel Get([FromUri]TestRequestModel request)
{
}
The key here is the [FromUri] which tells the model binder to look in the uri for the values. There is also [FromBody] if you want to put a value type in the body of a request.
We were seeing the same thing. In our case the problem was a complex object being passed into a get method. We needed to add a [FromUri] attribute in the parameter to that method.
http://forums.asp.net/t/1809925.aspx/1?GET+requests+with+complex+object+as+input+parameter
public class SearchController : ApiController
{
// added [FromUri] in beta to RC transition otherwise media type formatter error
public IQueryable<SearchResultEventModel> Get( [FromUri]SearchSpecModel search )
{
// ...
}
}
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.
When the Service layer is only executing a task (checking if Id exists, sending an email, etc.), what is the best way for it to let the controller know if there were any errors?
Two solutions I can think of:
Always passing in an extra "broken rules" parameter by reference to the methods in the Service layer which it would update if there were any error.
Have the Service raise an exception and having the controller do a try/catch.
Are either one of these two approaches recommended? If not, what approach could I take to have the Service layer let the controller know what something went wrong (such as invalid parameter)?
Your service should collection all the broken rules and after that throw the "BrokenRuleException". Your controller will catch the "BrokenRuleException" and then use the brokenrules to update the user interface.
I created interface:
public interface IModelStateWrapper
{
void AddModelError(string name, string error);
}
Then I created implementation for every controller:
public class ControllerModelStateWrapper : IModelStateWrapper
{
private ModelStateDictionary _dictionary;
public ControllerModelStateWrapper(ModelStateDictionary dictionary)
{
_dictionary = dictionary;
}
public void AddModelError(string name, string error)
{
if (_dictionary[name] == null)
_dictionary.Add(name, new ModelState());
_dictionary[name].Errors.Add(error);
}
}
Every service implements:
public interface IModelWrapperService
{
IModelStateWrapper ModelWrapper {get;set;}
}
And then I set it in Controller:
public UserController(IUserService service)
{
_service.ModelWrapper = new ControllerModelStateWrapper(ModelState);
}
IModelStateWrapper is not the best name, because this interface can work not only with Controller.ModelState. Works pretty ok. You can easily replace IModelStateWrapper with mock or other implementation in your service tests. This solution also automatically sets ModelState as invalid.
I think that throwing the BrokenRuleException is a good choice.
Personally, I don't like to put state in a service, it's often a singleton (performed by a DI container), and only has other singletons collaborators (in my case, domain objects).