I have a method which save an image file in the database as a BLOB file. The method works fine, but when I get the callback in ExtJS filefield component, it always goes through failure function and I don't know what I have to respond to go through success function, this is my code:
Server method:
#POST
#Path("/upload")
#Consumes(MediaType.MULTIPART_FORM_DATA)
#Produces({ MediaType.APPLICATION_JSON })
public ServiceResponse uploadFile(#QueryParam("id") Long iconId, FormDataMultiPart form) {
CatIcon icon;
if (iconId != null) {
icon = catIconBean.getOne(iconId);
} else {
icon = new CatIcon();
}
byte[] image = form.getField("iconBmp").getValueAs(byte[].class);
if (image.length != 0) {
MultivaluedMap<String, String> headers = form.getField("iconBmp").getHeaders();
String type = headers.getFirst("Content-type");
List<String> list = Arrays.asList("image/gif", "image/png", "image/jpg", "image/jpeg",
"image/x-icon", "image/bmp");
if (list.contains(type)) {
icon.setIconBmp(image);
icon.setType(type);
}
}
icon.setDescription(form.getField("description").getValue());
icon.setFileName(form.getField("fileName").getValue());
icon = catIconBean.saveIcon(icon);
ServiceResponse sr = new ServiceResponse();
sr.httpResponse = true;
return sr;
}
What I have to return in the code above?
Client:
uploadIcon : function(item, e, eOpts) {
var me = this;
var form = this.getDetail().getForm();
var valid = form.isValid();
if (!valid) {
return false;
}
var values = form.getValues();
if(values) {
form.submit({
url : myApplication.defaultHost() + 'icon/upload?id=' + values.id,
waitMsg : 'Uploading...',
success : function(form, action) {
me.onCompleteSaveOrDelete();
},
failure : function(form, action) {
me.onCompleteSaveOrDelete();
}
});
}
},
I write the same function, me.onCompleteSaveOrDelete(), in both callback functions to make it be called, that's the method which I want to be called in success.
Greetings.
UPDATE:
I did almost the same Alexander.Berg answered. The only difference was that I write #Produces({ MediaType.APPLICATION_JSON }) instead of #Produces({ MediaType.TEXT_HTML }), because I need Json Response. But when I debug in chrome and check the response, I get this:
In failure:
failure : function(form, action) {
me.onCompleteSaveOrDelete();
}
In action param, within responseText:
"{"data":"{\"success\":true}","httpResponse":true,"totalCount":0}"
But It's still going through failure...I think I'm very close, any help??
Greetings.
The fileupload in Extjs is more tricky, because it is using iframe and submit, not a real ajax request for uploading files.
Try this on Server method:
#POST
#Path("/upload")
#Consumes(MediaType.MULTIPART_FORM_DATA)
#Produces(MediaType.TEXT_HTML)
public String uploadFile(#QueryParam("id") Long iconId, FormDataMultiPart form) {
(...)
JSONObject json = new JSONObject();
json.put("success", true);
json.put("msg", "Success");
return json.toString();
}
this is because the upload accepts Content-Type text/html,
see Example at http://docs.sencha.com/extjs/4.2.2/#!/api/Ext.form.field.File -> Example Usage -> Live Preview
Use Firefox browser with Firebug plugin and on Net tab the following URL -> http://docs.sencha.com/extjs/4.2.2/photo-upload.php
Response Headersview source
(...)
Content-Type text/html
(...)
Request Headersview source
Accept text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
(...)
Related
I already have two working bots in Wikipedia, but they use different method.
Currently, I am trying to create a bot using C#. On the API documentation page, they have provided some sample codes in java, PHP, and python. But unfortunately, they haven't provided any sample code in C#. It is the only language I am familiar with.
This is the Wikimedia API:Edit documentation page. Would someone kindly convert the few words from java (or any other language) to C#?
If I could get only this code converted to C#, I can build the rest of the bot by myself.
I asked help on the relevant noticeboard(s) on the wikimedia site(s), but nobody there is familiar with C#. That's why I am now asking this outside of wikipedia.
Thanks a lot in advance
This is the java source-code:
```
/*
edit.js
MediaWiki API Demos
Demo of `Login` module: Sending post request to login
MIT license
*/
var request = require( 'request' ).defaults( { jar: true } ),
url = 'https://test.wikipedia.org/w/api.php';
// Step 1: GET request to fetch login token
function getLoginToken() {
var params = {
action: 'query',
meta: 'tokens',
type: 'login',
format: 'json'
};
request.get( { url: url, qs: params }, function ( error, res, body ) {
var data;
if ( error ) {
return;
}
data = JSON.parse( body );
loginRequest( data.query.tokens.logintoken );
} );
}
// Step 2: POST request to log in.
// Use of main account for login is not
// supported. Obtain credentials via Special:BotPasswords
// (https://www.mediawiki.org/wiki/Special:BotPasswords) for lgname & lgpassword
function loginRequest( loginToken ) {
var params = {
action: 'login',
lgname: 'bot_username',
lgpassword: 'bot_password',
lgtoken: loginToken,
format: 'json'
};
request.post( { url: url, form: params }, function ( error, res, body ) {
if ( error ) {
return;
}
console.log( body );
} );
}
// Start From Step 1
getLoginToken();
I can't go so deeply but I hope you can take a look at this page and find out how to do it
https://learn.microsoft.com/en-us/dotnet/api/system.net.http.httpclient?view=net-6.0
You can try something like this
private static async Task LoginRequest(string loginToken)
{
var data = new
{
action = "login",
lgname = "bot_username",
lgpassword = "bot_password",
lgtoken = loginToken,
format = "json"
};
using (var client = new HttpClient())
using (var response = await client.PostAsJsonAsync(LOGIN_URL, data))
{
var responseString = await response.Content.ReadAsStringAsync();
var responseObject = JsonConvert.DeserializeObject<JObject>(responseString);
Console.WriteLine(responseObject);
}
}
private static async Task<string?> GetLoginToken()
{
var url = $"{LOGIN_URL}?action=query&meta=tokens&type=login&format=json";
using (var client = new HttpClient())
using (var response = await client.GetAsync(url))
{
var responseString = await response.Content.ReadAsStringAsync();
var responseObject = JsonConvert.DeserializeObject<JObject>(responseString);
return responseObject?["query"]?["tokens"]?["logintoken"]?.ToString();
}
}
For converting the response you need
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
You can call these methods from your Main method like this:
const string LOGIN_URL = "https://test.wikipedia.org/w/api.php";
public static async Task Main()
{
var loginToken = await GetLoginToken();
await LoginRequest(loginToken ?? throw new Exception("No login token."));
}
I am sending Json data but when I verify my method in my controller it arrives as null
html:
<div class="hijo">
#Html.LabelFor(m => m.Proyecto, new { #class = "", style = "color:#040404" })
#Html.DropDownListFor(Model => Model.ProyectoID, Model.Proyecto, "Seleccionar")
#Html.ValidationMessageFor(Model => Model.Proyecto, null, new { #class = "label label-danger", id = "Proyecto" })
</div>
<div class="hijo">
<input type="button" id="adicionar" value="Agregar" class="myButton">
</div>
JS:
$('#adicionar').click(function () {
debugger;
var data= {
Proyecto: $("#ProyectoID option:selected").text()
};
$.ajax({
url: "../ManejoDatos",
type: "POST",
dataType: "json",
data: JSON.stringify(data),
success: function (mydata) {
$("#UpdateDiv").html(mydata);
history.pushState('', 'New URL: ' + href, href); // This Code lets you to change url howyouwant
}
});
}
My Controller
public JsonResult ManejoDatos(string Proyecto)
{
........
...
return Json(Proyecto);
}
Console
I don't know if it's something very simple to fix but I don't see the error
regards
I found your JavaScript code is absolutely fine. The weird things is controller with your format ghostly doesn't work But I got success below way:
[System.Web.Http.HttpPost]
public HttpResponseMessage ManejoDatos(string Proyecto)
{
var data = " my test data ";
return Request.CreateResponse(HttpStatusCode.OK, data);
}
I will investigate that obviously and update the answer again .
Hope above idea help you to resolve your problem. Let me know if you encounter any more problem.
Simple types like string,int,float etc are to be fetched from query string in .Net framework where as complex types always fetched from body.
Solution-1
In this case .Net Framework is expecting string Proyecto from query parameters and you can call your API like this without making any change to your server side code
Note:Also you don't need to define key of the parameter (in http request) which is being defined as [From Body]. As total of one simple type can be passed using [FromBody] attribute which you can see below like this =ProyectoValue
POST ../ManejoDatos HTTP/1.1
Host: localhost:59353
Content-Type: application/x-www-form-urlencoded
=ProyectoValue
Solution-2
Or you can call your API by putting all your params in complex model and pass params through body instead of query parameters like this
Complex Type Model
public class MyModel
{
public string Proyecto{ get; set; }
}
API Code
public JsonResult ManejoDatos(MyModel myModel)
{
//Access using mymodel.Proyecto
........
...
return Json(mymodel.Proyecto);
}
And now you can consume your API by passing paramters through body like this
POST ../ManejoDatos HTTP/1.1
Host: localhost:59353
Content-Type: application/x-www-form-urlencoded
proyecto=myproyecto
I'm trying to insert into a FormData an array of arrays and a string, however java seems to not receive it , I have no log error in my Java server however I have a 500 Internal Server Error in my JavaScript console.
Here is the code for my controller :
#RequestMapping(value = "/getReporting", method = RequestMethod.POST)
#ResponseBody
public void getReporting(#RequestParam RecommendationForm form, #RequestParam String type, HttpServletResponse response) throws ApcException {
System.out.println("prova");
Map.Entry<String, byte[]> result = this.reportingService.getReporting(form,type);
try {
response.setHeader(//
"Content-Disposition",//
"attachment; filename=" +"bobo.xlsx");
response.setContentType("Application/x");
response.getOutputStream().write(result.getValue());
response.flushBuffer();
} catch (IOException e) {
e.printStackTrace();
}
}
}
And here is my service in Angular :
public getExcel(form: FormData): Observable<HttpResponse<Blob>> {
return this.http.post('/SV-AUD/api/reporting/getReporting', form, {observe: 'response', responseType: 'blob'});
}
And the component where I append the info in the formData :
form: FormGroup = this._fb.group(
{
hello1: [],
hello2: [],
hello3: [],
hello4: [],
hello5: [],
hello6: [],
hello7: [],
hello8: [],
hello9: [],
}
);
exportExcel() {
const formData: FormData = new FormData();
formData.append('form', this.form.getRawValue());
if (this.detailedType) {
formData.append('type', 'detailed');
} else {
formData.append('type', 'list');
}
this.reportingService.getExcel(formData).subscribe(data => {
const ctHeader = data.headers.get('content-disposition');
if (ctHeader) {
const filename = ctHeader.split('=')[1];
saveAs(data.body, filename);
}
});
}
The behavior that you are describing suggests that Spring is unable to bind your #RequestParam parameters of your getReporting method to the incoming request.
That means that the data that you are posting from the Angular side does not match up with what is expected on the Spring side.
Unless it's a typo, I'm guessing that the problem is this line in your component's source code, which does nothing (and should be a syntax error due to mis-matched parens):
(this.form.getRawValue()));
I'm guessing that it should be :
formData.append('form', (this.form.getRawValue()));
This will be a post where I ask the question and propose a solution
Since having had several trouble and having looked around a lot I decided to post my final solution for anyone else to take profit from it.
Question:
How to render google's reCaptcha v2.0 widget and verifying it in a Marionettejs app with a java back end.
After the common steps and following google guides to render the re captcha my captcha still didn't render, so here comes my solution:
Rendering the captcha and the inclusion of the script are both made inside the itemview onRender function:
'text!login/templates/form.html',
'app'
], function (app, Marionette, Backbone, _, $, Handlebars, FormTemplate) {
return Marionette.ItemView.extend({
template: Handlebars.compile(FormTemplate),
ui: {
form: '
},
events: {
'submit #ui.form': 'onSubmit'
},
onRender: function() {
this.loadCaptcha();
},
loadCaptcha: function() {
var self = this;
var getRecaptchaResponse = function(response) {
self.captchaResponse = response;
};
window.renderCaptcha = function () {
self.captchaWidgetId = grecaptcha.render('yourCaptchaDiv', {
sitekey: 'YourSiteKey',
callback: getRecaptchaResponse
});
};
$.getScript('https://www.google.com/recaptcha/api.js?onload=renderCaptcha&render=explicit', function() {});
},
...
}
I tried other ways of loading the script with several errors, like the script loaded before the div for it, or the browser says de Dom has completely loaded but the onRender gets called after
I had to include a div for the captcha widget to load in, this is in
form.html
<div id="reCaptcha" class="btn"></div>
That will have your widget rendered, now you need to both verify it has been filled and it is a valid user response with google, for this I use the same module and use the next function:
onSubmit: function (e) {
//only act if the captcha has been filled - This could be easily erased from a browser, but a back end verification takes place too
if (grecaptcha.getResponse() !== "") {
e.preventDefault();
var _view = this;
this.blockForm();
$.ajax({
url: 'yourLoginService',
type: 'POST',
data: {
userLogin: this.ui.user.val(),
userPassword: this.ui.password.val(),
//get the captcha response
captchaResponse: grecaptcha.getResponse()
}
}).done(function (data) {
app.router.navigate('', {trigger: true});
_view.destroy();
}).fail(function (jqXHR, textStatus, errorThrown) {
// your fail handling
});
}
},
Then comes the time to verify your captcha server side using the secret key provided by google (note this is a Java6 app, therefore the clumbersome exception Handling):
//some other imports ignored
import org.apache.commons.io.IOUtils;
import org.json.JSONException;
import org.json.JSONObject;
class Captcha {
private static final String CAPTCHA_SECRET_KEY = "YourSecretKey";
private static final Logger LOGGER = Logger.getLogger(Captcha.class);
static boolean isCaptchaValid(String response) {
try {
String url = "https://www.google.com/recaptcha/api/siteverify?"
+ "secret=" + CAPTCHA_SECRET_KEY
+ "&response=" + response;
InputStream res = new URL(url).openStream();
JSONObject json = new JSONObject(getJsonResponse(res));
res.close();
return json.getBoolean("success");
} catch (JSONException e) {
LOGGER.error("Can not parse captcha response Json: " + e);
return false;
} catch (MalformedURLException e) {
LOGGER.error("Malformed URL: " + e);
return false;
} catch (IOException e) {
LOGGER.error("Error reading response from captcha verification response: " + e);
return false;
}
}
private static String getJsonResponse(InputStream res) throws IOException {
BufferedReader rd = new BufferedReader(new InputStreamReader(res, Charset.forName("UTF-8")));
/*TODO in java 8+ use this and avoid using the external library
return rd.lines().collect(Collectors.joining());
*/
return IOUtils.toString(rd);
}
}
I have a layout page that has a form with AntiForgeryToken
using (Html.BeginForm(action, "Account", new { ReturnUrl = returnUrl }, FormMethod.Post, new { Id = "xcrf-form" }))
This generates a hidden field
<input name="__RequestVerificationToken" type="hidden" value="p43bTJU6xjctQ-ETI7T0e_0lJX4UsbTz_IUjQjWddsu29Nx_UE5rcdOONiDhFcdjan88ngBe5_ZQbHTBieB2vVXgNJGNmfQpOm5ATPbifYE1">
In my angular view (that is loaded in a div in the layout page, I do this
<form class="form" role="form" ng-submit="postReview()">
And my code for postReview() is as follows
$scope.postReview = function () {
var token = $('[name=__RequestVerificationToken]').val();
var config = {
headers: {
"Content-Type": "multipart/form-data",
// the following when uncommented does not work either
//'RequestVerificationToken' : token
//"X-XSRF-TOKEN" : token
}
}
// tried the following, since my other MVC controllers (non-angular) send the token as part of form data, this did not work though
$scope.reviewModel.__RequestVerificationToken = token;
// the following was mentioned in some link I found, this does not work either
$http.defaults.headers.common['__RequestVerificationToken'] = token;
$http.post('/Review/Create', $scope.reviewModel, config)
.then(function (result) {
// Success
alert(result.data);
}, function (error) {
// Failure
alert("Failed");
});
}
My MVC Create method is as follows
[HttpPost]
[ValidateAntiForgeryToken]
[AllowAnonymous]
public ActionResult Create([Bind(Include = "Id,CommentText,Vote")] ReviewModel reviewModel)
{
if (User.Identity.IsAuthenticated == false)
{
// I am doing this instead of [Authorize] because I dont want 302, which browser handles and I cant do client re-direction
return new HttpStatusCodeResult(HttpStatusCode.Forbidden);
}
// just for experimenting I have not yet added it to db, and simply returning
return new JsonResult {Data = reviewModel, JsonRequestBehavior = JsonRequestBehavior.AllowGet};
}
So no matter where I put the token, no matter what I use for 'Content-Type' (I tried application-json and www-form-urlencoded) I always get the error "The required anti-forgery form field "__RequestVerificationToken" is not present."
I even tried naming __RequestVerificationToken and RequestVerificationToken
Why does my server not find the damn token?
I also looked at couple of links that ask you to implement your own AntiForgeryToeknVerifyAttrbute and verify the token that is sent as cookieToken:formToken, I have not tried that but why I am not able to get it working whereas this works for the MVC controllers (non-angular posts)
Yes. By default, MVC Framework will check for Request.Form["__RequestVerificationToken"].
Checking the MVC source code
public AntiForgeryToken GetFormToken(HttpContextBase httpContext)
{
string value = httpContext.Request.Form[_config.FormFieldName];
if (String.IsNullOrEmpty(value))
{
// did not exist
return null;
}
return _serializer.Deserialize(value);
}
You need to create your own filter to check it from Request.Header
Code Snippet from Phil Haack's Article - MVC 3
private class JsonAntiForgeryHttpContextWrapper : HttpContextWrapper {
readonly HttpRequestBase _request;
public JsonAntiForgeryHttpContextWrapper(HttpContext httpContext)
: base(httpContext) {
_request = new JsonAntiForgeryHttpRequestWrapper(httpContext.Request);
}
public override HttpRequestBase Request {
get {
return _request;
}
}
}
private class JsonAntiForgeryHttpRequestWrapper : HttpRequestWrapper {
readonly NameValueCollection _form;
public JsonAntiForgeryHttpRequestWrapper(HttpRequest request)
: base(request) {
_form = new NameValueCollection(request.Form);
if (request.Headers["__RequestVerificationToken"] != null) {
_form["__RequestVerificationToken"]
= request.Headers["__RequestVerificationToken"];
}
}
public override NameValueCollection Form {
get {
return _form;
}
}
}
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class,
AllowMultiple = false, Inherited = true)]
public class ValidateJsonAntiForgeryTokenAttribute :
FilterAttribute, IAuthorizationFilter {
public void OnAuthorization(AuthorizationContext filterContext) {
if (filterContext == null) {
throw new ArgumentNullException("filterContext");
}
var httpContext = new JsonAntiForgeryHttpContextWrapper(HttpContext.Current);
AntiForgery.Validate(httpContext, Salt ?? string.Empty);
}
public string Salt {
get;
set;
}
// The private context classes go here
}
Check out here for MVC 4 implementation, to avoid salt issue
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class,
AllowMultiple = false, Inherited = true)]
public sealed class ValidateJsonAntiForgeryTokenAttribute
: FilterAttribute, IAuthorizationFilter
{
public void OnAuthorization(AuthorizationContext filterContext)
{
if (filterContext == null)
{
throw new ArgumentNullException("filterContext");
}
var httpContext = filterContext.HttpContext;
var cookie = httpContext.Request.Cookies[AntiForgeryConfig.CookieName];
AntiForgery.Validate(cookie != null ? cookie.Value : null,
httpContext.Request.Headers["__RequestVerificationToken"]);
}
}
I had the same problem. Turned out that I don't need to set antiforgery token anywhere explicitly in my angular js code. The MVC controller expects this token value to be delivered from 1. the form field, 2. cookie. The filter equates and is happy when they match.
When we submit the form, hidden field for the anti forgery token automatically supplies its value. Cookie is automatically set by the browser. So as I said, we don't need to do anything explicitly.
The problem really is request's content-type. By default it goes as as application/json and therefore the a.f. token value (or rather any form data) is not received.
Following worked for me:
// create the controller
var RegisterController = function ($scope, $http) {
$scope.onSubmit = function (e) {
// suppress default form submission
e.preventDefault();
var form = $("#registerform");
if (form.valid()) {
var url = form.attr('action');
var data = form.serialize();
var config = {
headers: {
'Content-type':'application/x-www-form-urlencoded',
}
};
$http.post(url, data, config).success(function (data) {
alert(data);
}).error(function(reason) {
alert(reason);
});
}
};
};
As Murali suggested I guess I need to put the toekn in the form itself, so I tried putting the token as part of form data and I needed to encode the form data as explained in https://stackoverflow.com/a/14868725/2475810
This approach does not require any additional code on server side, also we do not need to create and join cookie and form token. Just by form-encoding the data and including token as one of the fields as explained in the answer above we can get it rolling.
You should perform the HTTP request in this way:
$http({
url: '/Review/Create',
data: "__RequestVerificationToken=" + token + "¶m1=1¶m2=2",
method: 'POST',
headers: {
'X-Requested-With': 'XMLHttpRequest',
'Accept': 'application/json, text/javascript, */*; q=0.01',
'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8'
}
}).success(function(result) {
alert(result.data);
}).error(function(error) {
alert("Failed");
});