I have the following REST endpoint:
#POST
#Path("/id/{id}/doSomething")
#Produces({ MediaType.APPLICATION_JSON })
#Consumes({ MediaType.APPLICATION_JSON })
public Response doSomething(#PathParam("id") final String id, MyObject foo) {
// does some stuff; checks for a null foo and handles it
}
The MyObject class has a single String field called justification.
I'd like to be able to hit this endpoint with no content at all, or with JSON that maps to the MyObject class. I'd like either way to work. In other words, foo can be instantiated, or foo can be null; I have code to handle both cases.
The problem is that the JSON content appears to be required to this endpoint, not optional. So during testing, I'm having to send JSON to the endpoint, or I receive a 500 error. Even if that JSON is just {} (I can also send { justification: "blah blah" } and that works as well). But sending no content at all results in a failed call; never even hits the endpoint.
So, my question is, how can I set this endpoint up so that I can POST to it with no content at all, or with JSON in the body that maps to foo, and have either way work?
Ultimately, I just need a way for the user to be able to send a justification to this endpoint, but not have to. And because justifications can be long, I can't have it as a query param or a path param.
Thanks!
You are not going to be able to hit the endpoint with no content at all because your endpoint says #Consumes({MediaType.APPLCIATION_JSON}). Besides there has to be some content while you're trying to POST to a class while using a web service.
Like you said, even if it is a NULL or a {}, it doesn't matter as long as it has some content coming in.
Passing no content to the service works only when you're making a GET request.
For all other HTTP methods such as POST,PUT and DELETE, you will mandatorily HAVE to send some data.
As a solution to your problem, what you possibly could do is that - check if the content you have received is a NULL or a {} and do no processing at all for them.
If you still have a confusion in the answer, depending upon whether you're using SOAP or REST, this thread should help you.
How to express 'null' value from web service as real null or empty string instead of 'null' string
Hope this helps.
I was able to accomplish what I wanted by writing a second method annotated with the same REST path. This second method does not have an #Consumes statement, and does not have the second parameter in its method declaration. Looks like this:
#POST
#Path("id/{id}/doSomething")
#Produces({ MediaType.APPLICATION_JSON })
public Response doSomethingWithoutJustification(#PathParam("id") final String id) {
doSomething(id, null);
}
This new method maps to the same path, but does not expect JSON and doesn't expose a second parameter. So when I POST with nothing in the request body at all, it hits doSomethingWithoutJustification, and when I do provide JSON in the request body, it hits doSomething. Of course, if I provide anything other than valid JSON in the request body, I receive a 500 response from the service, as I'd expect.
I'd hoped to specify an optional parameter with a single method, but this solution works perfectly.
Related
I have a Core 2.2 Web API project as my back-end for an Angular front-end. On one screen I allow the user to select x amount of records for processing from a data grid. Before the records can be processed I need to check the database to see if records exist in another table by passing a list of 3 fields (intelligent key) to my API. I put this list into an object array, do a Json.stringify on that object and send it to my API as a Get request. This works fine as long as I select 1-3 records. As soon as I select 4 or more records I get "Access to XMLHttpRequest at 'request url' from origin 'http://localhost:4200' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource."
My Cors policy indicates it should let anything through and I'm also confused by why 1-3 records works fine.
In my startup.cs -> ConfigureServices method I have the Cors policy defined like so:
services.AddCors(options =>
{
options.AddPolicy("CorsPolicy",
builder => builder.WithOrigins("http://localhost:4200")
.AllowAnyMethod()
.AllowAnyHeader()
.AllowCredentials());
});
In my Angular service I make this call which serializes by array (apiUrl is my url to call my API: https://localhost/api/controller)
getRecordsByIntelligentKey(intelligentKey: IntelligentKey[]): Observable<Record[]>{
const jsonObject = JSON.stringify(intelligentKey);
const url = `${apiUrl}/${jsonObject}`;
return this.http.get<Record[]>(url).pipe(
tap(_ => console.log('fetching records based on intelligent key')),
catchError(this.handleError('getRecordsByIntelligentKey', []))
);
}
In my controller GET action I deserialize my string. I mean I'd like to pass an object but I think I need to do a POST for that.
[HttpGet("{jsonObject}")]
public ActionResult<List<Commission>> GetByCustomerAndTerritoryManager(string jsonObject)
{
try
{
var intelligentKey = JsonConvert
.DeserializeObject<List<ThreeFields>>(jsonObject);
return _repo.GetRecordsByIntelligentKey(intelligentKey);
}
catch (Exception ex)
{
_logger.Error(ex, "Error retrieving records.");
throw;
}
}
Now my problem is the user could select 1000s of records. When I select over 1000 records I just get ERR_CONNECTION_RESET probably due to the querystring being way too long.
I'm thinking I need an object but everything I've researched seems to advise against doing that with a GET and using the POST request instead. Problem is, it's a restful API and I'm already using the POST request for the processing portion. I guess I could use PUT or DELETE but it just feels wrong. I'm going to wire up the PUT right after I post this question to see if it will work but ultimately I'd like to find the correct solution for this.
UPDATE: The PUT method works fine even with over 1000 records selected so I guess this will be my interim solution for now. I still feel like there's code smell and would love to use a GET but at least this allows me to proceed.
Following Darrel Miller's guide I'm posting raw data to my web api controller and persisting it. The content could be application/xml or application/json.
In the corresponding get method I retrieve the posted content, parsing to either XElement for XML content or JToken for json and returning OK(json) or OK(xml).
public IHttpActionResult Get()
{
// obtain parsed JToken or XElement
return OK(parsedObject);
}
The problem is that this doesn't obey the Accept Header, for example returning the original json when Accept is "application/xml". Is this by design or am I missing something?
I'm expecting this kind of behaviour.
Edit - the Accept header is obeyed if I amend the content passed to the OK method to parsedObject.ToString(), so there seems to be an issue with converting the JToken object to XML.
Are you sure you didn't remove the XmlSerializer from your formatters list?
Anyway you can always control the formatter that will be used for serialization at response level.
For example:
//forcing xml
HttpResponseMessage resp = Request.CreateResponse(HttpStatusCode.OK, result, new XmlMediaTypeFormatter());
return resp;
You can change the new XmlMediaTypeFormatter() with the instance of your current formatter for your actual configuration.
Check this link and this link for more details.
I am using an upload control to send a file to a JsonResult, but I am also sending up a JSON string as a second parameter. This is all getting posted with the Content-Type:multipart/form-data;
[HttpPost]
public JsonResult UploadDocument(HttpPostedFileBase file, DocumentViewModel model)
{ ... }
I know MVC is capable of binding directly to a viewmodel if the content type is set to application/json but I don't think it's possible for me to set that in this case.
Is there any way for me to get MVC to automatically bind my posted json string to model?
That's not possible out-of-the-box. You will have to manually deserialize the JSON string parameter that you would read from the request to your view model inside the controller action or write a custom model binder for it that will do the job. Ideally you shouldn't be posting the model data as a JSON string but rather respect the content type you specified : multipart/form-data. So the correct way to handle this scenario is to modify the client code that is sending the request in order to respect the content type.
As I was unable to change the content-type I found this blog to be exactly what i needed.
"... our whole request stream(data) won’t be json string. Only the guest parameter will be supplied as json string..."
http://ishwor.cyberbudsonline.com/2012/07/fun-with-aspnet-mvc-3-custom-json-model-binder.html
a Json WCF service method looks like this:
getFoo(MyDataType data){ ...
WCF automaticly translates the HTTP request that looks like:
randomGuy{
name:'John',
age:18
}
into the C# defined type
MyDataType{
string name {get;set;}
int age {get;set;}
}
But if I encrypt the request content, instead of receiving a request that looks like
randomGuy{
name:'John',
age:18
}
I will receive
wceweuicn23cb38v934vb398v03c264vb834gv3v7b3vb3647v34vb3v83vh38v43vhv3hv
Is there any way to first decrypt the request content into randomGuy{
name:'John',
age:18
} before WCF translates the JSON object into the .net defined type MyDataType?
To me, you have about 2 options:
Option 1: Use SSL for the connection so the message is protected.
Option 2: Intercept the message
In summary, I believe that the answer you are looking for are along the lines of an IClientMessageInspector which will allow you to manipulate messages and intercept them if neccessary on you WCF client. This article should explain it somewhat - enter link description here
Looks like the IDispatchMessageInspector or IDispatchMessageFormatter may help you in this case. And in general check the System.ServiceModel.Dispatcher namespace - there are other helpful staff.
The idea with IDispatchMessageInspector, that you are able to change (decrypt in your case) the incoming message before it's converted to a .NET object from JSON one.
I understand if I use springMVC and pass a json object to the controller, it will try to bind the json object to the controller pararmenter, but how to handle the binding error? I use something like this but seems not userful.
public String save(#RequestBody #Valid SomeList list, BindingResult result){
if(result.hasError()){
System.out.println(result);
}
}
Generally, you can return the same view that submitted the data. If you have <form:error> tags there, they will be displayed (because of the binding information).
But this is most certainly an ajax call, so what you can do is set a specific response status in the if body:
response.setStatus(HttpServletResponse.NOT_ACCEPTABLE);
and then look for that status code (406) in the ajax response handler. If you want precise validation information, you can try serializing the binding result itself as a response.
System.out.println will do next to nothing. Basically what it's saying is output the binding result to the server jvm std out.
Since you're returning a String, I'm going to assume you're returning a view name, so you might want to redirect the user to an error page.