Dears
I want to have abstract generic class with the following pattern
My phone model is as follows:
class Phone extends ModelInterface {
...
#override
static dynamic getErrorInstance(Map<String, dynamic> map) {
return Phone.fromErrors(map);
}
#override
static dynamic getResponseInstance(Map<String, dynamic> map) {
return Phone.fromMap(map);
}
}
As you can see the Phone model extends ModelInterface and there are two overriden methods getErrorInstance and getResponseInstance
These two methods are defined as static in the ModelInterface abstract class
abstract class ModelInterface {
static dynamic getErrorInstance(Map<String, dynamic> map) {}
static dynamic getResponseInstance(Map<String, dynamic> map) {}
}
What I wanted to do is to create a generic method that builds the object based on response type shown below
abstract class Base {
...
T getModel<T extends ModelInterface>(Map<String, dynamic> map) {
if (hasErrors(map)) {
return T.getErrorInstance(map);
}
return T.getResponseInstance(map);
}
}
And the client for this method getModel shown below
class UserAuth extends Base {
Future<Phone> registerPhone(Phone phone) async {
String url = Routes.phoneUrl;
String body = json.encode(phone.toMap());
final response = await http.post(url, body: body, headers: Routes.headers);
final responseBody = json.decode(response.body);
// here I want the generic type to be phone
return getModel<Phone>(responseBody);
}
}
However I am getting this error
Thanks
Actually I fixed it by passing in another generic Parameter such as below
T getModel<T extends ModelInterface>(Map<String, dynamic> map, T) {
if (hasErrors(map)) {
return T.getErrorInstance(map);
}
return T.getResponseInstance(map);
}
And for the client code I added the below
class UserAuth extends Base {
Future<Phone> registerPhone(Phone phone) async {
String url = Routes.phoneUrl;
String body = json.encode(phone.toMap());
final response = await http.post(url, body: body, headers: Routes.headers);
final responseBody = json.decode(response.body);
return getModel<Phone>(responseBody, Phone.empty());
}
}
Related
I'm trying to come up with a way to structure a restAPI client so that it is mockable, and easy to read. Preferably the syntax would closely mimic the way restAPI uris are built:
GET http://example.com/resource/id/subResource/id
In my project there are currently two resources: Plans and Serialnumbers
Plans are the root resource and serialnumbers can be assigned to them:
GET http://example.com/plans/id/serialnumbers/id
Ive come up with the follow set of classes (the actual HTTP calls to the client have been omitted)
abstract class ITrackerApiClient {
ITrackerApiPlans get plans;
}
abstract class ITrackerApiPlans {
Future<void> getAll();
Future<void> create();
ITrackerApiPlan byId(String id);
}
abstract class ITrackerApiPlan {
ITrackerApiPlan_Serialnumbers get serialnumbers;
Future<void> delete() async {}
Future<void> get() async {}
}
abstract class ITrackerApiPlan_Serialnumbers {
Future<void> getAll();
Future<void> create();
ITrackerApiPlan_Serialnumber byId(String id);
}
abstract class ITrackerApiPlan_Serialnumber {
Future<void> delete();
Future<void> get();
}
class TrackerApiClient implements ITrackerApiClient {
#override
ITrackerApiPlans get plans => TrackerApiPlans();
}
class TrackerApiPlans implements ITrackerApiPlans {
#override
Future<void> create() async {
//httpclient.put(uri)
}
#override
Future<void> getAll() async {
//httpclient.get(uri)
}
#override
ITrackerApiPlan byId(String id) {
return TrackerApiPlan(id);
}
}
class TrackerApiPlan implements ITrackerApiPlan {
final String id;
TrackerApiPlan(this.id);
#override
ITrackerApiPlan_Serialnumbers get serialnumbers => TrackerApiPlan_Serialnumbers(id);
#override
Future<void> delete() async {
//httpclient.delete(uri)
}
#override
Future<void> get() async {
//httpclient.get(uri)
}
}
class TrackerApiPlan_Serialnumbers implements ITrackerApiPlan_Serialnumbers {
final String planId;
TrackerApiPlan_Serialnumbers(this.planId);
#override
Future<void> create() async {
//httpclient.put(uri)
}
#override
Future<void> getAll() async {
//httpclient.get(uri)
}
#override
ITrackerApiPlan_Serialnumber byId(String id) {
return TrackerApiPlan_Serialnumber(planId, id);
}
}
class TrackerApiPlan_Serialnumber implements ITrackerApiPlan_Serialnumber {
final String planId;
final String id;
TrackerApiPlan_Serialnumber(this.planId, this.id);
#override
Future<void> delete() async {
//httpclient.delete(uri)
}
#override
Future<void> get() async {
//httpclient.get(uri)
}
}
This code structure allows me to do things like this:
class Test {
void foo() {
ITrackerApiClient client = TrackerApiClient();
client.plans.getAll();
client.plans.create();
client.plans.byId("id").get();
client.plans.byId("id").delete();
client.plans.byId("id").serialnumbers.getAll();
client.plans.byId("id").serialnumbers.create();
client.plans.byId("id").serialnumbers.byId("foo").get();
client.plans.byId("id").serialnumbers.byId("foo").delete();
}
}
I really like the readability of the client calls. I don't like the number of classes required but on the other hand it's not too much effort. The actual http calls I can either perform in each of those classes, or delegate a call to another "internalApiClient" class which contains methods to perform each of the http calls.
Does this seem like a good idea given the fact that the api will continue to grow and will in the end require many more classes?
I'm using the oneOf feature to define several possible schemas that can go into a request body property of my service. In the generated Java client code, the Java implementations of these schemas implement an interface, but when I send a request through, Jackson is trying to create an instance of the interface, instead of the concrete class.
Swagger-codegen version
<groupId>io.swagger.codegen.v3</groupId>
<artifactId>swagger-codegen-maven-plugin</artifactId>
<version>3.0.14</version>
Swagger declaration file content
schemas:
TestRequest:
description:
Test request
type:
object
required:
- criteria
properties:
criteria:
oneOf:
- $ref: '#/components/schemas/CriteriaA'
- $ref: '#/components/schemas/CriteriaB'
...
CriteriaA:
description: Criteria A
type: object
required:
- type
- query
properties:
type:
description: A description
type: string
enum:
- CriteriaA
query:
description: A query.
type: object
Steps to reproduce
The Java client code generated by swagger codegen looks like this:
Interface:
public interface OneOfTestRequestCriteria {}
Concrete class:
#Schema(description = "")
#javax.annotation.Generated(value = "io.swagger.codegen.v3.generators.java.JavaClientCodegen", date = "2020-01-28T13:06:29.942Z[Europe/London]")
public class CriteriaA implements OneOfTestRequestCriteria {
#JsonAdapter(TypeEnum.Adapter.class)
public enum TypeEnum {
CriteriaA("CriteriaA");
private String value;
TypeEnum(String value) {
this.value = value;
}
public String getValue() {
return value;
}
#Override
public String toString() {
return String.valueOf(value);
}
public static TypeEnum fromValue(String text) {
for (TypeEnum b : TypeEnum.values()) {
if (String.valueOf(b.value).equals(text)) {
return b;
}
}
return null;
}
public static class Adapter extends TypeAdapter<TypeEnum> {
#Override
public void write(final JsonWriter jsonWriter, final TypeEnum enumeration) throws IOException {
jsonWriter.value(enumeration.getValue());
}
#Override
public TypeEnum read(final JsonReader jsonReader) throws IOException {
String value = jsonReader.nextString();
return TypeEnum.fromValue(String.valueOf(value));
}
}
} #SerializedName("type")
private TypeEnum type = null;
#SerializedName("query")
private Object query = null;
public CriteriaA type(TypeEnum type) {
this.type = type;
return this;
}
#Schema(required = true, description = "")
public TypeEnum getType() {
return type;
}
public void setType(TypeEnum type) {
this.type = type;
}
public CriteriaA query(Object query) {
this.query = query;
return this;
}
#Schema(required = true, description = "")
public Object getQuery() {
return query;
}
public void setQuery(Object query) {
this.query = query;
}
#Override
public boolean equals(java.lang.Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
CriteriaA criteriaA = (CriteriaA ) o;
return Objects.equals(this.type, criteriaA.type) &&
Objects.equals(this.query, criteriaA.query);
}
#Override
public int hashCode() {
return Objects.hash(type, query);
}
#Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("class CriteriaA {\n");
sb.append(" type: ").append(toIndentedString(type)).append("\n");
sb.append(" query: ").append(toIndentedString(query)).append("\n");
sb.append("}");
return sb.toString();
}
private String toIndentedString(java.lang.Object o) {
if (o == null) {
return "null";
}
return o.toString().replace("\n", "\n ");
}
}
I'm trying to use this generated client code to send a request:
final TestRequest testRequest = new TestRequest();
final CriteriaA criteriaA = new CriteriaA ();
criteriaA .setType(CriteriaA .TypeEnum.CriteriaA);
criteriaA .setQuery("a query");
testRequest .setCriteria(criteriaA );
final ApiResponse<Void> apiResponse = testApi.createOrUpdateTestWithHttpInfo(testRequest);
Running the above client code results in this error when Jackson tries to deserialize it. It seems to be trying to construct an instance of the interface OneOfTestRequestCriteria, instead of the concrete implementation of the interface; CriteriaA:
[Request processing failed; nested exception is
org.springframework.http.converter.HttpMessageConversionException:
Type definition error: [simple type, class
com.acme.tag.models.OneOfTestRequestCriteria]; nested exception is
com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Cannot
construct instance of com.acme.tag.models.OneOfTestRequestCriteria (no
Creators, like default construct, exist): abstract types either need
to be mapped to concrete types, have custom deserializer, or contain
additional type information\n
If I annotate the generated interface:
public interface OneOfTestRequestCriteria {}
with the following:
#JsonTypeInfo(
use = JsonTypeInfo.Id.NAME,
include = JsonTypeInfo.As.PROPERTY,
property = "type")
#JsonSubTypes({
#Type(value = CriteriaA.class, name = "CriteriaA")
})
public interface OneOfTestRequestCriteria {
}
Then the request gets deserialized correctly into CriteriaA - am I missing something in my swagger.yaml that would result in this interface not getting annotated by the codegen tool?
<groupId>io.swagger.codegen.v3</groupId>
<artifactId>swagger-codegen-maven-plugin</artifactId>
<version>3.0.18</version>
See also: https://github.com/swagger-api/swagger-codegen-generators/pull/585
I have WebAPI (.NET Core) and use FluentValidator to validate model, including updating.
I use PATCH verb and have the following method:
public IActionResult Update(int id, [FromBody] JsonPatchDocument<TollUpdateAPI> jsonPatchDocument)
{
also, I have a validator class:
public class TollUpdateFluentValidator : AbstractValidator<TollUpdateAPI>
{
public TollUpdateFluentValidator ()
{
RuleFor(d => d.Date)
.NotNull().WithMessage("Date is required");
RuleFor(d => d.DriverId)
.GreaterThan(0).WithMessage("Invalid DriverId");
RuleFor(d => d.Amount)
.NotNull().WithMessage("Amount is required");
RuleFor(d => d.Amount)
.GreaterThanOrEqualTo(0).WithMessage("Invalid Amount");
}
}
and map this validator in Startup class:
services.AddTransient<IValidator<TollUpdateAPI>, TollUpdateFluentValidator>();
but it does not work. How to write valid FluentValidator for my task?
You will need to trigger the validation manually.
Your action method will be somthing like this:
public IActionResult Update(int id, [FromBody] JsonPatchDocument<TollUpdateAPI> jsonPatchDocument)
{
// Load your db entity
var myDbEntity = myService.LoadEntityFromDb(id);
// Copy/Map data to the entity to patch using AutoMapper for example
var entityToPatch = myMapper.Map<TollUpdateAPI>(myDbEntity);
// Apply the patch to the entity to patch
jsonPatchDocument.ApplyTo(entityToPatch);
// Trigger validation manually
var validationResult = new TollUpdateFluentValidator().Validate(entityToPatch);
if (!validationResult.IsValid)
{
// Add validation errors to ModelState
foreach (var error in validationResult.Errors)
{
ModelState.AddModelError(error.PropertyName, error.ErrorMessage);
}
// Patch failed, return 422 result
return UnprocessableEntity(ModelState);
}
// Map the patch to the dbEntity
myMapper.Map(entityToPatch, myDbEntity);
myService.SaveChangesToDb();
// So far so good, patch done
return NoContent();
}
You can utilize a custom rule builder for this. It might not be the most elegant way of handling it but at least the validation logic is where you expect it to be.
Say you have the following request model:
public class CarRequestModel
{
public string Make { get; set; }
public string Model { get; set; }
public decimal EngineDisplacement { get; set; }
}
Your Validator class can inherit from the AbstractValidator of JsonPatchDocument instead of the concrete request model type.
The fluent validator, on the other hand, provides us with decent extension points such as the Custom rule.
Combining these two ideas you can create something like this:
public class Validator : AbstractValidator<JsonPatchDocument<CarRequestModel>>
{
public Validator()
{
RuleForEach(x => x.Operations)
.Custom(HandleInternalPropertyValidation);
}
private void HandleInternalPropertyValidation(JsonPatchOperation property, CustomContext context)
{
void AddFailureForPropertyIf<T>(
Expression<Func<T, object>> propertySelector,
JsonPatchOperationType operation,
Func<JsonPatchOperation, bool> predicate, string errorMessage)
{
var propertyName = (propertySelector.Body as MemberExpression)?.Member.Name;
if (propertyName is null)
throw new ArgumentException("Property selector must be of type MemberExpression");
if (!property.Path.ToLowerInvariant().Contains(propertyName.ToLowerInvariant()) ||
property.Operation != operation) return;
if (predicate(property)) context.AddFailure(propertyName, errorMessage);
}
AddFailureForPropertyIf<CarRequestModel>(x => x.Make, JsonPatchOperationType.remove,
x => true, "Car Make cannot be removed.");
AddFailureForPropertyIf<CarRequestModel>(x => x.EngineDisplacement, JsonPatchOperationType.replace,
x => (decimal) x.Value < 12m, "Engine displacement must be less than 12l.");
}
}
In some cases, it might be tedious to write down all the actions that are not allowed from the domain perspective but are defined in the JsonPatch RFC.
This problem could be eased by defining none but rules which would define the set of operations that are valid from the perspective of your domain.
Realization bellow allow use IValidator<Model> inside IValidator<JsonPatchDocument<Model>>, but you need create model with valid properties values.
public class ModelValidator : AbstractValidator<JsonPatchDocument<Model>>
{
public override ValidationResult Validate(ValidationContext<JsonPatchDocument<Model>> context)
{
return _validator.Validate(GetRequestToValidate(context));
}
public override Task<ValidationResult> ValidateAsync(ValidationContext<JsonPatchDocument<Model>> context, CancellationToken cancellation = default)
{
return _validator.ValidateAsync(GetRequestToValidate(context), cancellation);
}
private static Model GetRequestToValidate(ValidationContext<JsonPatchDocument<Model>> context)
{
var validModel = new Model()
{
Name = nameof(Model.Name),
Url = nameof(Model.Url)
};
context.InstanceToValidate.ApplyTo(validModel);
return validModel;
}
private class Validator : AbstractValidator<Model>
{
/// <inheritdoc />
public Validator()
{
RuleFor(r => r.Name).NotEmpty();
RuleFor(r => r.Url).NotEmpty();
}
}
private static readonly Validator _validator = new();
}
You may try the below generic validator - it validates only updated properties:
public class JsonPatchDocumentValidator<T> : AbstractValidator<JsonPatchDocument<T>> where T: class, new()
{
private readonly IValidator<T> _validator;
public JsonPatchDocumentValidator(IValidator<T> validator)
{
_validator = validator;
}
private static string NormalizePropertyName(string propertyName)
{
if (propertyName[0] == '/')
{
propertyName = propertyName.Substring(1);
}
return char.ToUpper(propertyName[0]) + propertyName.Substring(1);
}
// apply path to the model
private static T ApplyPath(JsonPatchDocument<T> patchDocument)
{
var model = new T();
patchDocument.ApplyTo(model);
return model;
}
// returns only updated properties
private static string[] CollectUpdatedProperties(JsonPatchDocument<T> patchDocument)
=> patchDocument.Operations.Select(t => NormalizePropertyName(t.path)).Distinct().ToArray();
public override ValidationResult Validate(ValidationContext<JsonPatchDocument<T>> context)
{
return _validator.Validate(ApplyPath(context.InstanceToValidate),
o => o.IncludeProperties(CollectUpdatedProperties(context.InstanceToValidate)));
}
public override async Task<ValidationResult> ValidateAsync(ValidationContext<JsonPatchDocument<T>> context, CancellationToken cancellation = new CancellationToken())
{
return await _validator.ValidateAsync(ApplyPath(context.InstanceToValidate),
o => o.IncludeProperties(CollectUpdatedProperties(context.InstanceToValidate)), cancellation);
}
}
it has to be registered manually:
builder.Services.AddScoped<IValidator<JsonPatchDocument<TollUpdateAPI>>, JsonPatchDocumentValidator<TollUpdateAPI>>();
I want o post data on https://fcm.googleapis.com/fcm/send
along with two headers in Retrofit
Data to be sent
{
"data" : {
"title": "My Title",
"content": "My message"
},
"to": "cKA7LrjBQ6s:APA91bHtY6RBwZ4KZvxbl9VNZMVKz5_NDbE2dP3zgrhJNBSAKDyfOAbfxEi8pnAwc82pzLoGEZImZBv9MXvoBSJy6c0790oqUIYLECCU5WZVcGeSJJNECX5bsLMutYrSPjLSDffP5N3u"
}
It's very simple. Create the following classes.
public interface RestInterface {
#Headers({
"Content-Type: application/json",
"Authorization: key=<YOUR_FCM_SERVER_KEY_HERE>"
})
#POST("fcm/send")
Call<ResponseBody> sendNotification(#Body NotificationBody body);
}
Replace <YOUR_FCM_SERVER_KEY_HERE> with your actual FCM server key.
public class NotificationBody {
#SerializedName("data")
private Data data;
#SerializedName("to")
private String to;
public NotificationBody(Data data, String to) {
this.data = data;
this.to = to;
}
}
Above POJO class will generate outer JSONObject in run-time. And the following POJO class will generate data JSONObject.
public class Data {
#SerializedName("title")
private String title;
#SerializedName("content")
private String content;
public Data(String title, String content) {
this.title = title;
this.content = content;
}
}
And finally use above code in your Activity/Fragment classes like below,
String title = "My Title";
String content = "My message";
String to = "cKA7LrjBQ6s:APA91bHtY6RBwZ4KZvxbl9VNZMVKz5_NDbE2dP3zgrhJNBSAKDyfOAbfxEi8pnAwc82pzLoGEZImZBv9MXvoBSJy6c0790oqUIYLECCU5WZVcGeSJJNECX5bsLMutYrSPjLSDffP5N3u";
Data data = new Data(title, content);
NotificationBody body = new NotificationBody(data, to);
RestInterface api = ....;
Call<ResponseBody> call = api.sendNotification(body);
call.enqueue(new Callback<ResponseBody>() {
#Override
public void onResponse(Call<ResponseBody> call, final Response<ResponseBody> response) {
// do whatever you want to do
}
#Override
public void onFailure(Call<ResponseBody> call, Throwable t) {
Log.e("TAG", "Error: ", t);
}
});
And don't forget to set Retrofit BASE_URL to https://fcm.googleapis.com/
In previous MVC5 and below, you could make an ajax call that unwrapped the parameters properly:
JS:
$.post('/controller/endpoint',{intparam: 1, strparam: 'hello'})
CS:
public ActionResult endpoint(int intparam, string strparam){}
In the new aspnetcore, it has changed:
CS:
public CustomClassWrapper{
public int intparam {get;set;}
public string stringparam {get;set;}
}
public ActionResult endpoint([FromBody]CustomClassWrapper item){}
To sum it up, in the new framework, you need to write a wrapper class and can only pass one [FromBody] parameter to the method. Previously, the params would be unwrapped by variable name correctly.
So, i'm trying to re-implement this functionality in an aspnetcore middleware component. I'm having difficulty in how to accomplish calling the controller method properly with the parameters.
My current cut-down code:
public async Task Invoke(HttpContext context)
{
if (IsAjaxRequest(context.Request))
{
try
{
string bodyContent = new StreamReader(context.Request.Body).ReadToEnd();
var parameters = JsonConvert.DeserializeObject(bodyContent);
///What to do here?
}
catch (Exception ex)
{
throw new Exception("AJAX method not found ", ex);
}
}
else
{
await _next(context);
}
}
I'm really just not sure about what to do after deserializing the parameters. I have the URL for the endpoint and also the params correctly. Just need to know how to call the method and return the result as JSON. Should i be using Reflection to get the controller method? Or is there a better way using MVC?
Try implement custom IModelBinder.
public class BodyFieldModelBinder : IModelBinder
{
public Task BindModelAsync(ModelBindingContext bindingContext)
{
bindingContext.HttpContext.Request.EnableRewind(); // required to read request body multiple times
var inputStream = bindingContext.HttpContext.Request.Body;
if (inputStream.Position != 0L)
inputStream.Position = 0;
var bodyValue = new StreamReader(inputStream, Encoding.UTF8).ReadToEnd();
var jsonObject = (JObject)JsonConvert.DeserializeObject<object>(bodyValue);
if (jsonObject.TryGetValue(bindingContext.FieldName, out var jToken))
{
var jsonSerializer = JsonSerializer.Create();
var result = jToken.ToObject(bindingContext.ModelType, jsonSerializer);
bindingContext.Result = ModelBindingResult.Success(result);
return Task.CompletedTask;
}
bindingContext.Result = ModelBindingResult.Failed();
return Task.CompletedTask;
}
}
Be careful, the code above lacks error handling and etc.
And use it like this:
[HttpPost]
public IActionResult Endpoint([ModelBinder(typeof(BodyFieldModelBinder))] int intparam)
Also you could implement custom attribute to reduce complexity of declaration:
public class BodyFieldAttribute : ModelBinderAttribute
{
public BodyFieldAttribute()
: base(typeof(BodyFieldModelBinder))
{
}
}
it's very simple thing i don't know why it not working at your end
JS
$.post('actionMethodURl', { FirstName: '1', LastName: 'hello' }).done(Successfunction);
CS
[HttpPost]
public ActionResult endpoint(string FirstName,string LastName)
{
object Message = string.Empty;
if (ModelState.IsValid)
{
Message = "Pass";
}
else
{
Message = ModelState.Errors();
}
return Json(Message);
}