Retrofit: How do I set a custom deserializer for a certain API calls only? - retrofit

Hi so i have seen this example pretty much everywhere where they write a custom GSON deserialiser and then its added to the RestAdapter
Basically like this
public RestClient()
{
Gson gson = new GsonBuilder()
.registerTypeAdapterFactory(new ItemTypeAdapterFactory()) // This is the important line ;)
.setDateFormat("yyyy'-'MM'-'dd'T'HH':'mm':'ss'.'SSS'Z'")
.create();
RestAdapter restAdapter = new RestAdapter.Builder()
.setLogLevel(RestAdapter.LogLevel.FULL)
.setEndpoint(BASE_URL)
.setConverter(new GsonConverter(gson))
.setRequestInterceptor(new SessionRequestInterceptor())
.build();
apiService = restAdapter.create(ApiService.class);
}
But what if my API service cannot use the same deserializer for all API calls? What if i only wanted a single API call to use a speific deserializer?

As far as I know from some tests, you are forced to create two different RestAdapter instances, one with the Gson converter and one without it.

Related

Autofac Multiple Regsistrations to Single service. Simple Injector -> Autofac translation

I've developed a CQRS style database access framework based on Tripod and other inspirations but targeting .NET Standard and simplifying for easier use. I want to split the IoC into separate integration packages so consumers can get the type registration I'm currently doing internally easily without being locked into a specific IoC container. My issue is I've only really worked closely with SimpleInjector so not familiar with other systems and their nuances around how they handle specific scenarios. I have an iminent need to support Autofac so thought I'd try here to see if anyone can translate.
I have the following Simple Injector CompositionRoot static class:
public static void RegisterDatabase(this Container container, DbContextOptions<EntityDbContext> dbContextOptions, params Assembly[] assemblies)
{
var scopedLifeStyle = container.Options.DefaultScopedLifestyle;
//container.Register<ICreateDbModel, DefaultDbModelCreator>(scopedLifeStyle); // lifestyle c
container.RegisterInitializer<EntityDbContext>( //(container.InjectProperties);
handlerToInitialise => handlerToInitialise.ModelCreator = new DefaultDbModelCreator()
);
// Setup DbContext
var ctxReg = scopedLifeStyle.CreateRegistration(
() => new EntityDbContext(dbContextOptions),
container);
container.AddRegistration<IUnitOfWork>(ctxReg);
container.AddRegistration<IReadEntities>(ctxReg);
container.AddRegistration<IWriteEntities>(ctxReg);
}
In ASP.NET Core solutions I invoke the above from Startup.Configure(...) with:
var optionsBuilder = new DbContextOptionsBuilder<EntityDbContext>()
//.UseInMemoryDatabase("Snoogans");
.UseSqlServer(_config.GetConnectionString("DefaultConnection"));
container.RegisterDatabase(optionsBuilder.Options);
which allows me to switch out to an in memory database for unit testing if needed. EntityDbContext contains all my unit of work methods for calling onto the context without having to specify explicit DbSet for each table. The IUnitOfWork, IReadEntities and IWriteEntities interfaces all define methods on the EntityDbContext.
So I'm not sure how I'd go about making an Autofac module that allows scoped registration of the dbcontext with passed in DbContextOptions followed by multiple registrations of interfaces to this registration.
Does anyone know how this can be achieved?
I worked out the process and now have an AutoFac module. I was able to registermodule by instance of the class and also pass in the options when I instantiate. Because EntityDbContext implements the three interfaces I was registering separately in the Simple Injector scenario, AutoFac has the convenience of being able to just infer them and register with AsImplementedInterfaces()
public class EntityFrameworkModule : Module
{
private readonly DbContextOptions<EntityDbContext> _dbContextOptions;
public EntityFrameworkModule(DbContextOptions<EntityDbContext> dbContextOptions)
{
_dbContextOptions = dbContextOptions;
}
protected override void Load(ContainerBuilder builder)
{
// If the calling code hasn't already registered a custom
// ICreateDbModel then register the internal DefaultDbModelCreator
builder.RegisterType<DefaultDbModelCreator>()
.IfNotRegistered(typeof(ICreateDbModel))
.As<ICreateDbModel>();
// Expecting IUnitOfWork, IReadEntities and IWriteEntities to be registered with this call
builder.Register(c => new EntityDbContext(_dbContextOptions)
{
ModelCreator = c.Resolve<ICreateDbModel>()
})
.AsImplementedInterfaces()
.InstancePerLifetimeScope();
}
}

How to mange API endpoints?

Configured the HttpClient in the startup.cs.
services.AddHttpClient("jsonPosts", client => {
client.BaseAddress = new Uri("https://jsonplaceholder.typicode.com/");
On the Controller calling API:
// Obtaining _clientFactory by DI on the Controller constructor
var client = _clientFactory.CreateClient("jsonPosts");
var myContent = JsonConvert.SerializeObject(myObjectToSerialize);
HttpContent stringContent = new StringContent(myContent, Encoding.UTF8, "application/json");
HttpResponseMessage result = await client
.PostAsync(client.BaseAddress + "posts/1", stringContent)
.ConfigureAwait(false);
You can can see on the PostAsync method the API endpoint is being appended to the base address of the HttpClient.
Is this the recommended approach of managing different endpoints across an application?
Well, that depends on your application.
If you only have to do few things like authenticate, post something, exit application then there´s no reason to do the work and create a structure thatfor.
If you do multiple calls and especially want to do the same call at different points in your code you should create an api wrapper.
A common way is to create one generic method that takes an Type as generic argument, also give it the url, HTTP method and other data you might need.
The method will do the call with the arguments given, automatically Deserialize the JSON to an Object of the generic type and return it to you.
This way you can do something like this with only having to write one method and define classes for the Results. You might even use dynamics without defining classes but I personally don´t like dynamics.
ApiClient api = new ApiClient(baseUrl);
User user = api.get<User>("/user", new Query().add("user", "admin"));
EmailList emails = api.get<EmailList>("/user/emails");
Then you could still populate it into multiple methods if you don´t want to mess with the endpoints like
public User getUser(String username){
User user = api.get<User>("/user", new Query().add("user", "admin"));
return user;
}
And use it like
MyApiWrapper.getUser("admin");

How can I parse ODataQueryOptions from a string?

I have to provide some read endpoints for our EF6 entities on an ASP.NET API that conform to the OData specification. Entity retrieval works well based upon functions that accept a System.Web.Http.OData.Query.ODataQueryOptions<TEntity> instance.
Now, according to the docs, that implementation of OData does not support $count.
We would, however, like to offer at least the capability to retrieve the total count of a (filtered) data set as shown in the docs, like (by slightly combining several of those samples):
http://host/service/Products/$count($filter=Price gt 5.00)
(Based on the spec, I understand that this should be a valid, specification-conformant OData query for the number of products whose price is greater than 5¤. Please correct me if I'm wrong.)
Now, retrieving the count based on the IQueryable returned from ODataQuerySettings.ApplyTo is trivial. So is capturing requests to this route:
[Route("$count({queryOptions})")]
public int Count(ODataQueryOptions<ProductEntity> queryOptions)
The only bit that is missing is that the queryOptions portion of the route should be parsed into the ODataQueryOptions<ProductEntity> instance. On other OData endpoints, this works without any further ado. However, even when I call this endpoint with a $filter, all I am getting is an "empty" (i.e. initialized to default values) ODataQueryOptions<ProductEntity> instance with no filter set.
Alternatively, I can declare my web API endpoint like this:
[Route("$count({queryOptions})")]
public int Count(string rawQueryOptions)
Within this method, rawQueryOptions contains the query options that I wish to pass to OData, that is, parse to populate an ODataQueryOptions<ProductEntity> instance.
This must be very straightforward as the very same thing happens for any other OData endpoint. For a comparison:
[Route("")]
public IEnumerable<object> Filter(ODataQueryOptions<ProductEntity> queryOptions)
This works; the query options are populated as expected, unlike it is the case with my above endpoint.
How can I populate my OData query options instance based on the string extracted from my route?
Some more things I have tried:
Applied [FromUri] to the queryOptions parameter.
Applied [ODataQueryParameterBinding] to the queryOptions parameter.
Although the syntax is a little bit different to your request, the .Net OData Implementation has the support you need OOTB, if you're asking this question at all, that indicates that you are trying to add OData support to your standard API.
Given that you have EF6 already on an ASP.Net API... Why not just expose the OData controllers on another route? In this way you get the full implementation of query support without ever needing to parse the QueryOptions at all.
Updated
If adding new controllers in a separate route is not suitable you can easily upgrade your existing ApiControllers and Implement OData routes in place of the existing ones.
ODataController inherits from ApiController but includes some helper methods that simplify working with OData response conventions, so upgrading in place is generally non-breaking.
As an example, the following is the only controller code that is needed to allow all the supported OData Query Options to return data from an EF DbSet, this includes full support for $select, $expand, $filter, $apply and even $count across a nested $filter.
public partial class ResidentsController : ODataController
{
protected MyEF.Context db = new MyEF.Context();
[EnableQuery]
public async Task<IHttpActionResult> Get(ODataQueryOptions<MyEF.Resident> options)
{
return Ok(db.Residents);
}
}
The magic that allows this is not the ODataController itself, the EnableQueryAttribute parses/translates the QueryOptions and applies this to the Linq to Entities expression that is returned from the method.
The final component to make this work is to register the routes, this is a little bit more involved than standard API because you need to define the EdmModel first, but in doing so we never need to parse the incoming query parameters.
a minimal example to configure the model and routes for the above controller might look like this:
public static void Register(HttpConfiguration config)
{
var builder = new ODataConventionModelBuilder();
builder.EntitySet<Resident>("Residents");
IEdmModel model = builder.GetEdmModel();
// To enable $select and $filter on all fields by default
config.Count().Filter().OrderBy().Expand().Select().MaxTop(null);
// can also be configured like this
config.SetDefaultQuerySettings(new Microsoft.AspNet.OData.Query.DefaultQuerySettings()
{
EnableCount = true,
EnableExpand = true,
EnableFilter = true,
EnableOrderBy = true,
EnableSelect = true,
MaxTop = null
});
// Map the routes from the model using OData Conventions
config.MapODataServiceRoute("odata", "odata", model);
}
How to Configure the $count syntax you desire
although your expected syntax for count of filtered collections is not supported OOTB, the syntax that is supported is very close, so you could easily manipulate the query with a URL re-write module
Your expected syntax:
http://host/service/Products/$count($filter=Price gt 5.00)
.Net Supported syntax
http://host/service/Products/$count?$filter=Price gt 5.00
OwinMiddleware:
/// <summary>
/// Rewrite incoming OData requests that are implemented differently in the .Net pipeline
/// </summary>
public class ODataConventionUrlRewriter : OwinMiddleware
{
private static readonly PathString CountUrlSegments = PathString.FromUriComponent("/$count");
public ODataConventionUrlRewriter(OwinMiddleware next)
: base(next)
{
}
public override async Task Invoke(IOwinContext context)
{
// OData spec says this should work: http://host/service/Products/$count($filter=Price gt 5.00)
// But in .Net the filter needs to be in the query: http://host/service/Products/$count?$filter=Price gt 5.00
var regex = new System.Text.RegularExpressions.Regex(#"\/\$count\((.+)\)$");
var match = regex.Match(context.Request.Path.Value);
if(match != null && match.Success)
{
// So move the $filter expression to a query option
// We have to use redirect here, we can't affect the query inflight
context.Response.Redirect($"{context.Request.Uri.GetLeftPart(UriPartial.Authority)}{regex.Replace(context.Request.Path.Value, "/$count")}?{match.Groups[1].Value}");
}
else
await Next.Invoke(context);
}
}
Add to Startup.cs, before registering OData routes
app.Use(typeof(ODataConventionUrlRewriter));

Use same REST representations using Graphaware Neo4j Framework as the ones we get from the Neo4j REST API

I am developing a Neo4j server extension using the Neo4j Framework provided by Graphaware.
I want in my response to send the following object (simplified so that you can see the attributes) :
public class DiffResult {
private Node fileOrFolder;
private Node originalContent;
private Path path;
}
The problem is that the Node object cannot be rendered by Jackson. I have seen a NodeRepresentation class somewhare but I also don't know how to use it properly with my Spring MVC Controller.
I want my nodes to be serialized like in the Neo4j REST Api (cf documentation: http://neo4j.com/docs/stable/rest-api-nodes.html#rest-api-get-node)
I also show you the controller I am using (also simplified).
#Controller
#RequestMapping("/diff")
public class FileSpaceDiffApi {
private final GraphDatabaseService database;
#Autowired
public FileSpaceDiffApi(GraphDatabaseService database) {
this.database = database;
}
#RequestMapping(method = RequestMethod.GET)
#ResponseBody
public List<DiffResult> diff(#QueryParam("fileSpaceId") Long fileSpaceId, #QueryParam("since") Long since) {
List<DiffResult> results = new ArrayList<DiffResult>();
Transaction tx = database.beginTx();
try {
Node startNode = database.getNodeById(fileSpaceId);
DiffResult diffResult = new DiffResult();
diffResult.setFileOrFolder(startNode);
results.add(diffResult);
tx.success();
}
finally {
tx.close();
}
return results;
}
}
Ideally I'd also like to be able to render the Path in JSON.
There is (yet) no capability of easily returning nodes in the same format as Neo4j does. This is mainly because the Neo4j REST API is very generic and thus too chatty and verbose for many use-cases.
I would suggest looking at com.graphaware.api.JsonNode to which you can pass a Neo4j node and some configuration about what will be present in the generated JSON (e.g. whether to include labels, etc.)
You can use it by adding the following to your pom.xml:
<dependency>
<groupId>com.graphaware.neo4j</groupId>
<artifactId>api</artifactId>
<version>${graphaware.version}</version>
</dependency>
As for paths, there is a JsonPath class in neo4j-algorithms, that will help you achieve what you want. We will happily move to the core framework for the next release (that's where it really should be).

How can I avoid a 406 when receiving an OData.PageResult<T>?

I have an ODataController that is returning a PageResult.
Api Example:
public PageResult<Customer> Get(ODataQueryOptions options) {
// cut some stuff out...
PageResult<Customer> result = new PageResult<Customer>(
searchResults as IEnumerable<Customer>,
Request.GetNextPageLink(),
Request.GetInlineCount());
return result;
When I debug this, it seems to be fine and have a PageResult class built up correctly to return. On the Web side..
Web Example
using (var client = new HttpClient()) {
client.BaseAddress = new Uri(testURL);
string searchUrl = "api/customer?$top=1&$skip=0";
client.DefaultRequestHeaders.Accept.Add(MediaTypeWithQualityHeaderValue.Parse("application/json;odata=verbose"));
HttpResponseMessage response = client.GetAsync(searchUrl).Result;
The response is a StatusCode 406, with a reason phrase stating the content was not acceptable. It also does this if I define a new MediaTypeWithQualityHeaderValue("application/json").
What do I need to change so that I successfully consume this Api in the controller before passing it on to the view?
I think you are missing the first two steps of building an OData service. ODataController, as the name says, only works with OData routes. You need to build an EDM model representing your OData service, and, add an OData route exposing that EDM model. Refer to this official documentation and blog post for details on how to build OData services.

Resources