WCF REST service 400 Bad Request - android

I have WCF RESTful service and Android client.
Server replies with 400 when I do bigger request. It seems that I have 65k limit issue like
in here or in other million posts on same problem.
However, I can't seem to be able to fix it. Here is how my web.config looks
<system.serviceModel>
<diagnostics>
<messageLogging logMalformedMessages="true" logMessagesAtServiceLevel="true"
logMessagesAtTransportLevel="true" />
</diagnostics>
<serviceHostingEnvironment aspNetCompatibilityEnabled="true"/>
<standardEndpoints>
<webHttpEndpoint>
<standardEndpoint name="myEndpoint" helpEnabled="true" automaticFormatSelectionEnabled="true" maxReceivedMessageSize="1000000" />
</webHttpEndpoint>
</standardEndpoints>
</system.serviceModel>
Here is code example of service function:
[WebInvoke(UriTemplate = "/trips/{TripId}/inspection", Method = "POST")]
[Description("Used on mobile devices to submit inspections to server")]
public void PostTripInspection(string tripId, Inspection inspection)
{
return;
}
Here is code inside my Web project which hosts WCF (Global.asax.cs)
private static void RegisterRoutes()
{
// Setup URL's for each customer
using (var cmc = new CoreModelContext())
{
foreach (var account in cmc.Accounts.Where(aa => aa.IsActive).ToList())
{
RouteTable.Routes.Add(
new ServiceRoute(
account.AccountId + "/mobile", new WebServiceHostFactory(), typeof(MobileService)));
}
}
}
From what I understand Java HttpClient doesn't impose any limits so it's on WCF side. Any pointers on how to solve this issue or how to intercept message in WCF?
EDIT 2:
This is what trace shows. And when I modigy standardEndpoint it doesn't help...

Forgive me if you've seen this link (Similar StackOverflow Question):
By default the WCF Transport is
limited to sending messages at 65K. If
you want to send larger you need to
enable Streaming Transfer Mode and
you need to increase the size of
MaxReceivedMessageSize, which is there
just as a guard to prevent someone
killing your server by uploading a
massive file.
So, you can do this using binding
configuration or you can do it in
code. Here is one way to do it in
code:
var endpoint = ((HttpEndpoint)host.Description.Endpoints[0]); //Assuming one endpoint
endpoint.TransferMode = TransferMode.Streamed;
endpoint.MaxReceivedMessageSize = 1024 * 1024 * 10; // Allow files up to 10MB

You don't need to use streaming in this case - all you need to do is to increase the maxReceivedMessageSize quota on the standard webHttpEndpoint:
<standardEndpoints>
<webHttpEndpoint>
<!--
Configure the WCF REST service base address via the global.asax.cs file and the default endpoint
via the attributes on the <standardEndpoint> element below
-->
<standardEndpoint name=""
helpEnabled="true"
automaticFormatSelectionEnabled="true"
maxReceivedMessageSize="1000000"/>
</webHttpEndpoint>
</standardEndpoints>
Update: if the config change didn't work (I don't know why), you can try increasing it in code. By using a custom service host factory, you get a reference to the endpoint object and you can increase the quota there. The code below shows one such a factory (you'll need to update the RegisterRoute code to use this new factory):
public class MyWebServiceHostFactory : ServiceHostFactory
{
protected override ServiceHost CreateServiceHost(Type serviceType, Uri[] baseAddresses)
{
return base.CreateServiceHost(serviceType, baseAddresses);
}
class MyWebServiceHost : WebServiceHost
{
public MyWebServiceHost(Type serviceType, Uri[] baseAddresses)
: base(serviceType, baseAddresses)
{
}
protected override void OnOpening()
{
base.OnOpening();
foreach (ServiceEndpoint endpoint in this.Description.Endpoints)
{
if (!endpoint.IsSystemEndpoint)
{
Binding binding = endpoint.Binding;
if (binding is WebHttpBinding)
{
((WebHttpBinding)binding).MaxReceivedMessageSize = 1000000;
}
else
{
CustomBinding custom = binding as CustomBinding;
if (custom == null)
{
custom = new CustomBinding(binding);
}
custom.Elements.Find<HttpTransportBindingElement>().MaxReceivedMessageSize = 1000000;
}
}
}
}
}
}

Related

How to handle X-CSRF-TOKEN correctly in an angular-based cordova app?

I have an Angular (v10) WebApp, which handles the X-CSRF-TOKEN cookie correctly as explained in the Angular Guide by using the HttpClientXsrfModule in my imports, i.e.:
// app.module.ts
HttpClientModule,
HttpClientXsrfModule.withOptions({
cookieName: 'XSRF-TOKEN',
headerName: 'X-XSRF-TOKEN',
}),
and by setting an relative Path in my services' requests, like:
// some service.ts
public deleteX(x_id: number): Observable<any> {
return this.httpClient.delete(`api/X/${x_id}`);
}
and now, the browser itself handles fetching the token from the server and sending it by each subsequent POST/DELETE/PUT/PATCH request successfully.
However, if I compile the application now to an Android app using cordova, the app sends a request (with x_id=1068) to file:///android_asset/www/api/X/1068.
I can modify my http services to use platform-specific absolute/relative paths easily, such as:
// some service.ts
public deleteX(x_id: number): Observable<any> {
if (this.cordovaService.platform === CordovaService.PLATFORM_ANDROID) {
return this.httpClient.delete(`${environment.baseUrl}/api/X/${x_id}`);
} else {
return this.httpClient.delete(`api/X/${x_id}`);
}
}
But then, my request's response from the Android application is
error: "access_denied"
error_description: "Invalid CSRF Token 'null' was found on the request parameter '_csrf' or header 'X-XSRF-TOKEN'."
What can I do, to add correct handling of the X-XSRF-TOKEN for my cordova compiled Android app?
I ended up using the cordova-plugin-advanced-http, that is offering a response-cookie-fetching opportunity described here.
I've created a generic-http-service containg generic methods for each of the HTTP methods (GET,HEAD,PATCH,PUT,POST,DELETE), that is first checking for the running platform and then forwarding an adjusted request.
example for generic GET (pseudo-code):
// generic-http-service.ts
public get<T>(url: string, queryParams?: any): Observable<T> {
if (this.cordovaService.platform === CordovaService.PLATFORM_ANDROID) {
// android-specific solution
// 1. adjust params
// 2. set general headers + the XSRF-TOKEN from the previous sendt request
// 3. return Observable(obs) {
// 4. send:
cordova.plugin.http.get(`${environment.baseUrl}/${url}`, adjustedParams, adjustedHeaders,
successResponse => {
// 5. fetch & save XSRF-TOKEN
obs.next(JSON.parse(successResponse.data) as T);
}, errorResponse => {
obs.error(errorResponse);
})
}
} else {
// web-specific solution based on the angular guide
return this.httpClient.get(`${url}`);
}
}
Afterwards I just needed to adjust my services a little bit.

odata4j requests metadata too many times

I use the odata4j library to access a WCF Data Service.
This is how I call a Service Method from my Android code:
OQueryRequest<OEntity> l = consumer.getEntities("GetDataList")
.custom("dataId", String.format("'%s'", actualData.ID))
.orderBy("Name").skip(0).top(200);
I checked it with WireShark, and I see that every method call is preceded with 2 calls of metadata information request:
Why? Are they essential? The metadata information is quite heavy, it shouldn't request is every time (not to mention 2 times).
What should I do to prevent odata4j from requesting metadata information so many times?
I found in the source code where the 'extra' request happens (in odata4j/odata4j-core/src/main/java/org/odata4j/consumer/AbstractODataConsumer.java ):
#Override
public EdmEntitySet findEdmEntitySet(String entitySetName) {
EdmEntitySet rt = super.findEdmEntitySet(entitySetName);
if (rt == null && delegate != EdmDataServices.EMPTY) {
refreshDelegate();
rt = super.findEdmEntitySet(entitySetName);
}
return rt;
}
It seems that if the entity set can't be found, the consumer creates an extra roundtrip to the server to get the metadata again (by calling refreshDelegate()):
private void refreshDelegate() {
ODataClientRequest request = ODataClientRequest.get(AbstractODataConsumer.this.getServiceRootUri() + "$metadata");
try {
delegate = AbstractODataConsumer.this.getClient().getMetadata(request);
} catch (ODataProducerException e) {
// to support services that do not expose metadata information
delegate = EdmDataServices.EMPTY;
}
}
I don't quite understand why: maybe it assumes that the server has changed and a new version of the metadata is available so it tries again.
If it fails then it tries to find a function with the given name.
Personally I don't consider this very effective unless the server side is so volatile that it changes between calls.
So, if you have no changing metadata on the server, it is safe to remove the check for the entitySet and let it return as a null:
#Override
public EdmEntitySet findEdmEntitySet(String entitySetName) {
EdmEntitySet rt = super.findEdmEntitySet(entitySetName);
//if (rt == null && delegate != EdmDataServices.EMPTY) {
// refreshDelegate();
// rt = super.findEdmEntitySet(entitySetName);
//}
return rt; //if it is null, then the search for a function will commence
}

How do I send a serialized object over network stream to a tcp listener in Xamarin?

Can someone show me a code example of how to send serialized objects back and forth from a Xamarin Android application using a BinaryFormatter instead of Json? It's going to be over WiFi inside the Server Farm.
I'm currently trying to port a simple administrative console application over to an Xamarin Android forms application. I don't understand PCL yet or it's lack of [serializable] attribute. I've heard from the guys at Xamarin that I should probably try Android specific Xamarin instead of forms. I'm new to this so I'm not sure. This will be connecting to a custom Windows Service using standard TCPListeners. Any help would be greatly appreciated. Thanks.
This is an example of the type of console code that I am trying to port over.
public static void HeartBeatPulseListener()
{
Int32 hbPort = 8002;
Console.WriteLine("\nStarting Heart Beat Listener on Port: {0}", hbPort.ToString());
TcpListener heartBeatListener = new TcpListener(IPAddress.Any, hbPort);
heartBeatListener.Start();
while (true)
{
using (TcpClient client = heartBeatListener.AcceptTcpClient())
{
Console.BackgroundColor = ConsoleColor.DarkRed;
Console.ForegroundColor = ConsoleColor.Yellow;
NetworkStream netStream = client.GetStream();
IFormatter formater = new BinaryFormatter();
HeartBeatPulse pulseMSG = (HeartBeatPulse)formater.Deserialize(netStream);
if (pulseMSG != null) Console.WriteLine("\nPulse:{0} \n tStamp:{1}\n FROM:{2}\n Instance:{3} \n Original Unique:{4} \n Type: {5}", pulseMSG.Id.ToString(), pulseMSG.TimeStamp.ToString(), pulseMSG.A.ToString(), pulseMSG.ServerCoreInstanceId, pulseMSG.OriginalUnique, pulseMSG.Type);
if (pulseMSG.Roles.Count() > 1)
{
Console.WriteLine("\nRoles:");
foreach (string role in pulseMSG.Roles)
{
Console.WriteLine("\n{0}", role);
}
}
else Console.WriteLine("\nSum Ting Wong");
Console.ResetColor();
}
}
}

How CakePhp 2.5 receive data post from Android?

I want to know how CakePhp 2.5 receive data post from Android
I want to know the following
1.URL to be sent from Android application
2.Methods of receiving data is POST (CakePhp)
I have tried many times,but I couldn't it.
Sending the data from the Android is neatly confirmed
1
Now I wrote the following URL.I don't know it is right.
http://*****/cake/books/test
2
BooksController.php
<?php
class BooksController extends AppController
{
public $name = 'Books';
public $uses = array('Book');
public function test()
{
$add = "";
if( isset($this->request->data['id']))
{
$add = $this->request->data['id'];
$this->set( 'address' , $add);
}
}
}
?>
test.ctp
<div>
<?php
if( isset($address) )
{
pr($address);
}
?>
</div>
In CakePHP you can use the following function to test is it is a POST request:
if($this->request->is('post')) {
...
}
Depending on the setup of your Webserver and CakePHP, the URL to post could also be:
http://*/books/test (without the cake part)
just order Android to post a URL example
www.sample.com/controller_name/function_name/123
, 123 is your code or variable
You can then use as simple function parameter
//in your controller
function register($uuid=null)
{
echo $uuid; //$uuid is value post by android, register() is function name
}

How to send SharedObject Array data to PHP WS for DB Query (Flex)

I am currently developing an Android application using Flex 4.5.1 and I am having an issue when trying to pass data that I have stored in a SharedObject array to my Web Service for a Database query. the code below shows how I am storing the data in the SharedObject:
var so:SharedObject = SharedObject.getLocal("app");
public var prefsArray:ArrayCollection = new ArrayCollection(so.data.prefs);
protected function prefs_btn_click(event:MouseEvent):void
{
prefsArray.source.push(getFrsByIDResult.lastResult.id);
so.data.prefs = [prefsArray];
var flushStatus:String = so.flush();
if (flushStatus != null) {
switch(flushStatus) {
case SharedObjectFlushStatus.PENDING:
so.addEventListener(NetStatusEvent.NET_STATUS,
onFlushStatus);
break;
case SharedObjectFlushStatus.FLUSHED:
trace("success");
break;
}
}
}
protected function onFlushStatus(event:NetStatusEvent):void
{
trace(event.info.code);
}
I have tested the SharedObject to see if the information is being entered into it correctly and all seems fine. Now I have used the code below in order to retrieve the data from the SharedObject and try and send it to the PHP web Service to run the DB query.
var so:SharedObject = SharedObject.getLocal("app");
var arrCol:ArrayCollection = new ArrayCollection(so.data.prefs);
var str:String = new String(arrCol.toString());
protected function list_creationCompleteHandler(event:FlexEvent):void
{
getPrefsByprefIdsResult.token = prefsService.getPrefsByPrefIds(so.data.prefs);
}
I have tested the Webservice in Flex and have it configured to recieve an Array of Ints (int[]) and it works when i run a test operation on it with two dummy values. However when I try to use the code above to pass the Web Service the Shared Object data I get this error:
TypeError: Error #1034: Type Coercion failed: cannot convert []#97e97e1 to mx.collections.ArrayCollection.
at views::**************/list_creationCompleteHandler()[C:\Users\Jack\Adobe Flash Builder 4.5\****************\src\views\*******************.mxml:25]
at views::*********************/__list_creationComplete()[C:\Users\Jack\Adobe Flash Builder 4.5\****************\src\views\***************.mxml:94]
at flash.events::EventDispatcher/dispatchEventFunction()
at flash.events::EventDispatcher/dispatchEvent()
at mx.core::UIComponent/dispatchEvent()[E:\dev\4.5.1\frameworks\projects\framework\src\mx\core\UIComponent.as:13128]
at mx.core::UIComponent/set initialized()[E:\dev\4.5.1\frameworks\projects\framework\src\mx\core\UIComponent.as:1818]
at mx.managers::LayoutManager/validateClient()[E:\dev\4.5.1\frameworks\projects\framework\src\mx\managers\LayoutManager.as:1090]
at mx.core::UIComponent/validateNow()[E:\dev\4.5.1\frameworks\projects\framework\src\mx\core\UIComponent.as:8067]
at spark.components::ViewNavigator/commitNavigatorAction()[E:\dev\4.5.1\frameworks\projects\mobilecomponents\src\spark\components\ViewNavigator.as:1878]
at spark.components::ViewNavigator/commitProperties()[E:\dev\4.5.1\frameworks\projects\mobilecomponents\src\spark\components\ViewNavigator.as:1236]
at mx.core::UIComponent/validateProperties()[E:\dev\4.5.1\frameworks\projects\framework\src\mx\core\UIComponent.as:8209]
at mx.managers::LayoutManager/validateProperties()[E:\dev\4.5.1\frameworks\projects\framework\src\mx\managers\LayoutManager.as:597]
at mx.managers::LayoutManager/doPhasedInstantiation()[E:\dev\4.5.1\frameworks\projects\framework\src\mx\managers\LayoutManager.as:783]
at mx.managers::LayoutManager/doPhasedInstantiationCallback()[E:\dev\4.5.1\frameworks\projects\framework\src\mx\managers\LayoutManager.as:1180]
I have replaced certain filenames and locations with *'s to protect the work i am doing, but can someone please help me with this issues as I believe it has to be something simple???
Thanks
ok so let me explain in more detail. This is being designed for an Android app like I said, but image what I am trying to do is to store Bookmarks persistently using the Local Shared Object.
The first chunck of code you see above is designed to create the LSO attribute for the bookmark i want to create and imagine that there can be more than one bookmark set at different times like in a web browser. The only way i could find to do this was to store these items/details in an array which I retrieve and then update before saving back to the LSO and saving.
The second piece of code related to imagine a "Bookmarks Page" with a list of all the content that I have bookmarked. Now what I wanted to happen was thta I would be able to call up the LSO attribute which held the id's of the bookmarks and then load up thier details in a list format.
I have managed to create the LSO and store the bookmark deatils in and allow them to be updated and entries added. Also I have made sure that the PHP code that I have pulls back all the database objects relating to the array of id's and this has been tested using flex. The only thing that I cant seem to do is to pass the id's to the PHP web service file. The code in the Web Service file is below if that helps:
public function getPrefsByPrefIds($PrefIds) {
$stmt = mysqli_prepare($this->connection, "SELECT * FROM $this->tablename WHERE $this->tablename.id IN(" .implode(",", $PrefIds). ")");
$this->throwExceptionOnError();
mysqli_stmt_execute($stmt);
$this->throwExceptionOnError();
$rows = array();
mysqli_stmt_bind_result($stmt, $row->id, $row->name, $row->desc);
while (mysqli_stmt_fetch($stmt)) {
$rows[] = $row;
$row = new stdClass();
mysqli_stmt_bind_result($stmt, $row->id, $row->name, $row->desc);
}
mysqli_stmt_free_result($stmt);
mysqli_close($this->connection);
return $rows;
}
Yes I had already tried that but thanks. I have made some more progress on my own as I have been experimenting with the different types of objects that can be stored in SharedObjects. I have managed to get the solution part working with this code:
This code is designed to capture the boomark info and store it in an arrayCollection before transferring it to a bytesArray and saving
var so:SharedObject = SharedObject.getLocal("app");
public var prefArray:ArrayCollection = new ArrayCollection(so.data.prefs);
protected function prefs_btn_click(event:MouseEvent):void
{
prefArray.source.push(getCompaniesByIDResult.lastResult.id);
so.data.prefs = [prefArray];
var bytes:ByteArray = new ByteArray();
bytes.writeObject(prefArray);
so.data.ac = bytes;
var flushStatus:String = so.flush();
if (flushStatus != null) {
switch(flushStatus) {
case SharedObjectFlushStatus.PENDING:
so.addEventListener(NetStatusEvent.NET_STATUS,
onFlushStatus);
break;
case SharedObjectFlushStatus.FLUSHED:
trace("success");
break;
}
}
}
protected function onFlushStatus(event:NetStatusEvent):void
{
trace(event.info.code);
}
This next code is the designed to retrieve that information from the SahredObjects bytesArray and put it back into an Array Collection
var so:SharedObject = SharedObject.getLocal("app");
var ba:ByteArray = so.data.ac as ByteArray;
var ac:ArrayCollection;
protected function list_creationCompleteHandler(event:FlexEvent):void
{
ba.position = 0;
ac = ba.readObject() as ArrayCollection;
getPrefsByPrefIdsResult.token = prefsService.getPrefsByPrefIds(ac);
}
however as I have said this works in a small way only as if I store only one Bookmark (id) for an item and then go to the bookmarks list the details for that bookark are successfully retrieved, however if I save more than one Bookmark(2 or more id's) the page will not load the details, i do not get an error but I believe it is hanging because it is looking for say id's "1,2" instead of "1" and "2" but i dont know why this is or how to resolve this. I appreciate the advice I have been given but am finding it hard there is no one who can help me with this issue and I am having to do various experiemnts with the code. Can someone please help me with this I would really appreciate it :-) Thanks

Categories

Resources