Using UI5 with a ASP.Net oData back-end - c#

I want to create a back-end by ASP.NET Web Application API for a SAPUI5 front-end.
The front-end side is so simple and works with mock-server fine, and has no problem with internal mock-server.
However I want to use the app with a real ASP.NET back-end server.
I used this tutorial for creating the ASP.NET back-end server.
The only part that is a little bit different from the original tutorial is the WebAppConfig.cs file which I revealed it here:
using CimplySkills_Backend.Models;
using Microsoft.AspNet.OData.Extensions;
using Microsoft.Data.Edm;
using Microsoft.Data.Edm.Csdl;
using Microsoft.OData.Edm.Csdl;
using System.Web.Http.OData.Batch;
using System.Web.Http.OData.Builder;
using System.Web.Http.OData.Extensions;
using System;
using System.Linq;
using System.Web.Http;
using System.Web.Http.Cors;
namespace CimplySkills_Backend
{
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
// allow CORS
var cors = new EnableCorsAttribute("*", "*", "*", "*");
config.EnableCors(cors);
// Web-API-Routen
config.MapHttpAttributeRoutes();
// define HTTP access
/*
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
*/
// enable batch
var odataBatchHandler = new DefaultODataBatchHandler(GlobalConfiguration.DefaultServer);
// enable queries
config.Select().Expand().Filter().OrderBy().MaxTop(null).Count();
// set entities
ODataConventionModelBuilder builder = new ODataConventionModelBuilder();
builder.EntitySet<CountriesSet>("CountriesSet");
builder.Entity<LocationsSet>().HasOptional(x => x.Country).CascadeOnDelete(false);
//Define version
Version odataVersion2 = new Version(2, 0);
builder.DataServiceVersion = odataVersion2;
builder.MaxDataServiceVersion = odataVersion2;
IEdmModel edmModel = builder.GetEdmModel();
edmModel.SetEdmVersion(odataVersion2);
edmModel.SetEdmxVersion(odataVersion2);
config.Routes.MapODataServiceRoute(
routeName: "ODataRoute",
routePrefix: "sap/opu/odata/cimtag/ODATA_SKILLS_SRV/",
// routePrefix: null,
model: edmModel,
batchHandler: odataBatchHandler);
// resolve "No non-OData HTTP route registered"
// config.EnableDependencyInjection();
}
}
}
Now the problem is when the UI5 app try to send a batch request to the back-end, it can send the request and access the end-point correctly, the response is also successfully received, however there is a problem in the response that it will repeat the request again and again, and the binding is not triggered. Look at the following picture:
First of all, it seems I am not the only person who had this problem, for example here somebody else also had the same problem.
I analyzed the response from this ASP.NET server with a same batch response to an SAP ABAP server (which is the standard back-end for SAPUI5 applications) and there are a few differences.
For example here a batch response in SAP:
--BD335BC5DB87B4F290C21DB6C0A9D3650
Content-Type: application/http
Content-Length: 538
content-transfer-encoding: binary
HTTP/1.1 200 OK
Content-Type: application/json
Content-Length: 347
dataserviceversion: 2.0
sap-metadata-last-modified: Tue, 07 Apr 2020 13:02:03 GMT
cache-control: no-store, no-cache
{
"d":{
"__metadata":{
"id":"https://xyz.de:5698/sap/opu/odata/sap/ZMJZ_ODATA_COMMON_SRV/AuthSet'ZMJZ_SECTY')", "uri":"https://xyz.de:5698/sap/opu/odata/sap/ZMJZ_ODATA_COMMON_SRV/AuthSet'ZMJZ_SECTY')", "type":"ZMJZ_ODATA_COMMON_SRV.Auth"
},
"Object":"ZMJZ_SECTY",
"FlagCreate":true,
"FlagRead":true,
"FlagUpdate":true,
"FlagDelete":true
}
}
--BD335BC5DB87B4F290C21DB6C0A9D3650--
And here a batch response from ASP.NET:
--batchresponse_988aa133-3a4a-413c-9109-f5f7786e7e78
Content-Type: application/http
Content-Transfer-Encoding: binary
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
DataServiceVersion: 2.0
Access-Control-Allow-Origin: *
Access-Control-Expose-Headers: *
{
"d":{
"results":[
{
"__metadata":{
},"ID":1,"name":"Germany"
},{
"__metadata":{
},"ID":2,"name":"France"
},{
"__metadata":{
},"ID":3,"name":"Iran"
},{
"__metadata":{
},"ID":4,"name":"USA"
}
]
}
}
--batchresponse_988aa133-3a4a-413c-9109-f5f7786e7e78--
If we look carefully, we will see there is a Content-Length property that is appear two times in the SAP response while it does not exist in the ASP.NET response at all. (It is only my guess that it is the source of the problem)
The first question of mien is how can I modify the batch response in ASP.NET to add the Content-Length in the response content also. (Please notice that the Content-Length exist inside the response header and what you see here is the response payload or the response content itself!)
The second question is more general, do you have any other suggestion for fixing this issue?

Related

404 error when posting to Web API 2 action using HttpClient

OK, there is lots going on here so I will try and keep my question and examples as simple as I can. With that in mind, please ask if you need any additional information or clarification on anything.
The code
I have a Web API 2 project which has a number of controllers and actions. The particular action I am having problems with is defined in the ContactController as follows:
[HttpPost]
public MyModel GetSomething(System.Nullable<System.Guid> uid)
{
return GetMyModel(uid);
}
In case it matters, my routing is setup as follows:
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = RouteParameter.Optional }
);
Now I have another project that is required to call the above action. For calling the Web API I am using HttpClient. Note that I have lots of other actions calls which are working correctly, so this isn't a connectivity issue.
The code I am using to call the Web API method is as follows:
using (HttpClient client = GetClient())
{
var obj = new List<KeyValuePair<string, string>> { new KeyValuePair<string, string>("uid", someGuid.ToString()) };
var response = client.PostAsync(path, new FormUrlEncodedContent(obj)).Result;
return response.Content.ReadAsAsync<T>().Result;
}
In this instance, path is basically:
localhost:12345/api/contact/getsomething
The problem
The PostAsync call Result (i.e. response in the above code) gives this message:
{StatusCode: 404, ReasonPhrase: 'Not Found', Version: 1.1, Content: System.Net.Http.StreamContent, Headers:
{
Pragma: no-cache
X-SourceFiles: =?UTF-8?B?QzpcRGV2ZWxvcG1lbnRcUHJvamVjdHNcTGltYVxMaW1hIHYzXERFVlxMaW1hRGF0YVNlcnZpY2VcYXBpXHVzZXJhY2Nlc3NcZ2V0bW9kdWxlc2FjY2Vzcw==?=
Cache-Control: no-cache
Date: Fri, 18 May 2018 10:25:49 GMT
Server: Microsoft-IIS/10.0
X-AspNet-Version: 4.0.30319
X-Powered-By: ASP.NET
Content-Length: 222
Content-Type: application/json; charset=utf-8
Expires: -1
}}
If I put a breakpoint inside the aciton then it doesn't fire. However, what I find strange is that when I call it, Visual Studio (2018) tells me that the specific action has a "failed request" on that specific action. So clearly it must know which method I am trying to call?
At this point I am running out of ideas on how to debug further. What am I doing wrong here?
in this case you can use the same endpoint as for getting and posting.
so you probably need:
[HttpGet]
public IActionResult Get(System.Nullable<System.Guid> uid)
{
return GetMyModel(uid); //make sure you got it, oterhwise return a NotFound()
}
[HttpPost]
public IActionResult Post(InputModel model)
{
_service.doMagicStuff();
return Ok();
}
cheers!
not sure but error may be because of you are passing keyvalue pair
var obj = new List<KeyValuePair<string, string>> { new KeyValuePair<string, string>("uid", someGuid.ToString()) };
var response = client.PostAsync(path, new FormUrlEncodedContent(obj)).Result;
instead of guild only i.e. only string value expected by function , so it will be
var response = client.PostAsync(path, new FormUrlEncodedContent(someGuid.ToString())).Result;
method should be
[HttpPost]
public MyModel GetSomething(string uid)
{
return GetMyModel(Guid.Parse( uid));
}
You are sending the guid with the FormUrlEncodedContent but the requests content type is application/json.
I recommend you to send it as a json like this
using (HttpClient client = GetClient())
{
var obj = new { uid = someGuid.ToString()) };
var json = JsonConvert.SerializeObject(obj);
var content = new StringContent(json, Encoding.UTF8, "application/json");
var result = client.PostAsync(path, content).Result;
return response.Content.ReadAsAsync<T>().Result;
}
Then in the api controller, use the FromBody attribute to declare that the parameter is read from the request body
[HttpPost]
public MyModel GetSomething([FromBody]RequestModel model)
{
return GetMyModel(model.uid);
}
public class RequestModel
{
public System.Nullable<System.Guid> uid { get; set; }
}
Also, if you only have one Post method in the contact controller the url localhost:12345/api/contact will be enough

API - HttpResponseMessage: (407) proxy authentication required

Edit 1: Other Controller
public class identityController : ApiController
{
[HttpGet]
public async Task<IHttpActionResult> getfullname(string firstName)
{
string name = firstName;
return Ok(name);
}
}
I have created a controller which uses an API from another solution.
Method that i use in the controller looks like below:
public class GetNameController : ApiController
{
[HttpGet]
public async Task<IHttpActionResult> CalculatePrice(string firstName)
{
string _apiUrl = String.Format("api/identity/getfullname?firstName={0}", firstName);
string _baseAddress = "http://testApp.azurewebsites.net/";
using (var client = new HttpClient())
{
client.BaseAddress = new Uri(_baseAddress);
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
HttpResponseMessage response = await client.GetAsync(_apiUrl);
if (response.IsSuccessStatusCode)
{
return Ok(response);
}
}
return NotFound();
}
The result of response.IsSuccessStatusCode is always false. When i check the response values i see this result:
{
StatusCode: 400, ReasonPhrase: 'Bad Request', Version: 1.1, Content: System.Net.Http.StreamContent, Headers:
{
Connection: close
Date: Thu, 21 Jul 2016 12:28:21 GMT
Server: Microsoft-HTTPAPI/2.0
Content-Length: 334
Content-Type: text/html; charset=us-ascii
}
}
What could i be missing?
string _apiUrl = String.Format("api/identity/{0}", firstName);
This is assuming that your url is correct, and your testapp is up and running. Even though when I hit it azure tells me your app is stopped. You will need to get your app started first, then change the string _apiUrl to the suggestion above.
http://testapp.azurewebsites.net/api/identity/getfullname?firstName=steve
Gives me this message
Error 403 - This web app is stopped.
The web app you have attempted to reach is currently stopped and does
not accept any requests. Please try to reload the page or visit it
again soon.
If you are the web app administrator, please find the common 403 error
scenarios and resolution here. For further troubleshooting tools and
recommendations, please visit Azure Portal.
So there are several things in your identity controller that are going on.
the functions name is getFullName. Since the word get is in the name of the function. Any httpget request will be routed to the function automagically. Thus making the [HttpGet] redundant. This only works if there is 1 and only 1 httpget request in your controller. If there are multiple you will need to fully qualify the url like you have done
Since youa re using the [httpget] method attribute I can assume you are using webapi2. That being the case and you are using a
primitive in your controller argument you can do notneed to fully
qualify the parameter name on your call. ?firstname={0} changes to
/{0}

Passing data between controllers ASP.net API

So I'm trying pass user data from one controller to another. I've been reading a lot about it here ---> http://www.dotnet-tricks.com/Tutorial/webapi/F2aL081113-Passing-multiple-complex-type-parameters-to-ASP.NET-Web-API.html and this guy seems to be doing exactly what I want to do, and he's using .PostAsJsonAsync to do it.
This is controller 1, which takes in user data and basically re-routes it to the other controller for authentication.
public ActionResult IDB(IDBUser i)
{
HttpClient client = new HttpClient();
client.BaseAddress = new Uri("http://localhost:64819");
HttpResponseMessage result = client.PostAsJsonAsync("/api/Participant/Authenticate", i).Result;
if (result.IsSuccessStatusCode)
{
return View();
}
else { // hits this breakpoint with 500 server error
return View("Index");
}
}
}
}
This seems to successfully make it to the other controller (below), as I'm getting a 500 error. However, I've got a breakpoint right at the beginning of the method, and it never seems to hit it.
[System.Web.Http.ActionName("Authenticate")]
[System.Web.Http.HttpPost]
public HttpResponseMessage Authenticate(IDBUser u)
{ //breakpoint it neve hits is on this line
bool Authenticate = false;
CredentialTester ct = new CredentialTester(u.password, u.username);
bool isAuthenticatedInt = ct.IntTest();
bool isAuthenticatedAcp = ct.AcpTest();
if (isAuthenticatedInt == true || isAuthenticatedAcp == true)
{
Authenticate = true;
return Request.CreateResponse(HttpStatusCode.OK, Authenticate);
}
else Authenticate = false;
return Request.CreateResponse(HttpStatusCode.NotAcceptable);
}
Any help is apprecitaed. Thanks everyone!
UPDATE
: I was able to look at the error and it is listed here:
{StatusCode: 500, ReasonPhrase: 'Internal Server Error', Version: 1.1, Content: System.Net.Http.StreamContent, Headers:{ Pragma: no-cache X-SourceFiles: =?UTF-8?B?QzpcVXNlcnNcYTU4NDYxOVxEb2N1bWVudHNcYXAxMDk1MTQtcWEtYXVvdG1hdGlvblxXaVFhLlZhbC1JZFxNVkNcV2lRYS5WYWwtSWQuV2ViU2VydmljZVxXaVFhLlZhbC1JZC5XZWJTZXJ2aWNlXFdpUWEuVmFsLUlkLldlYlNlcnZpY2VcYXBpXFBhcnRpY2lwYW50XEF1dGhlbnRpY2F0ZQ==?= Access-Control-Allow-Origin: * Access-Control-Allow-Headers: Content-Type, Accept Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS Cache-Control: no-cache Date: Thu, 12 May 2016 17:07:39 GMT Server: Microsoft-IIS/8.0 X-AspNet-Version: 4.0.30319 X-Powered-By: ASP.NET Content-Length: 2307 Content-Type: application/json; charset=utf-8 Expires: -1}}
it looks like there's a problem with my Access- Control-Origin. I've put the appropriate adjustments in my web.config file (I've had a similar error before) but no luck.

POST to ASP.NET Web API from Fiddler

I have an ASP.NET Web API. I am trying to POST a string to an endpoint. My ASP.NET Web API endpoint looks like the following:
[HttpPost]
public async Task<IHttpActionResult> Test(string name)
{
int i = 0;
i = i + 1;
return Ok();
}
In Fiddler, I execute the following request from the composer:
POST http://localhost:8089/api/MyApiController/test
If I remove "string name" as the parameter, I can successfully execute my API endpoint. When string name is there, I get a 405 error. So, I added the following in the "Request Body" section in Fiddler:
John
Unfortunately, that still causes a 405 to be thrown. I can't tell if I'm setting up my Web API endpoint wrong if I'm setting up my request in fiddler incorrectly. My full request looks like this:
POST http://localhost:8089/api/MyApiController/test HTTP/1.1
User-Agent: Fiddler
Host: localhost:8089
Content-Length: 26
Content-Type: application/json; charset=utf-8
{ "name" : "John"}
The response looks like this:
HTTP/1.1 405 Method Not Allowed
Cache-Control: no-cache
Pragma: no-cache
Allow: GET
Content-Type: application/json; charset=utf-8
Expires: -1
Server: Microsoft-IIS/8.0
X-SourceFiles: =?UTF-8?B?QzpcRWNvZmljXFNvbGlkUVxKTExcamxsLW1hcmtldHNwaGVyZVxXZWJzaXRlXGFwaVxTZWFyY2hBcGlcaW5kZXg=?=
X-Powered-By: ASP.NET
Date: Fri, 12 Dec 2014 15:51:50 GMT
Content-Length: 73
{"Message":"The requested resource does not support http method 'POST'."}
I don't understand why POST is allowed when I do not have a parameter. Yet, when I add a parameter, POST does not work.
[Update]
I added the following in my C# code:
var content = await Request.Content.ReadAsStringAsync();
I figured the JSON would be in the content. However, content is just an empty string.
Under the "Composer" tab:
I suggest you should try with following url
http://localhost:8089/api/MyApi/test
I think that problem is with route.
You should have some route like following as default route just have controller/id. This route comes before default route.
config.Routes.MapHttpRoute(
name: "DefaultApi1",
routeTemplate: "api/{controller}/{action}",
defaults: new { action="test" }
);
Also you action should look like this.
[HttpPost]
public async Task<IHttpActionResult> Test([Frombody]string name)
{
int i = 0;
i = i + 1;
return Ok();
}
in the body part of the composer try
{ "name" : "somtext"}
Below, I modified the datatype of the parameter from string to object, and had success.
public async Task<IHttpActionResult> Post( [ FromBody ] **object** items )
{
Console.WriteLine( items );
return Ok();
}
In the body you need to add Json or XML syntax, not just then name for The Asp.Net Deserialiser to do its thing. Json would be:
{ "name" : "John" }
EDIT: Your url route also seems incorrect, if you are using the default routes. By default it will be picked up as: POST http://localhost:8089/api/MyApiController/ the "test" part is omitted as it recognises this as a POST method. Your action should also specify that the parameter you take in ("name") is expected from the body public async Task<IHttpActionResult> Test([FromBody]string name). Now your url will match if you do something like:
`POST http://localhost:8089/api/MyApiController/`
User-Agent: Fiddler
Host: localhost:8089
Content-Length: 26
{ "name" : "John" }

HttpSelfHostServer not recognizing "application/x-www-form-urlencoded" Requests

I'm working with an HttpSelfHostServer in .Net 4.5 and it seems to only be able to determine the controller and action when I send a request using QueryString. It doesn't work if I use "application/x-www-form-urlencoded".
Here's the HttpSelfHostServer code.
private static HttpSelfHostConfiguration _config;
private static HttpSelfHostServer _server;
public static readonly string SelfHostUrl = "http://localhost:8989";
internal static void Start()
{
_config = new HttpSelfHostConfiguration(SelfHostUrl);
_config.HostNameComparisonMode = HostNameComparisonMode.Exact;
_config.Routes.MapHttpRoute(
name: "API Default",
routeTemplate: "api/{controller}/{action}",
defaults: new { action = RouteParameter.Optional },
constraints: null);
_server = new HttpSelfHostServer(_config);
_server.OpenAsync().Wait();
}
The controller code.
public class SettingsController : ApiController
{
[HttpPost]
public bool Test(bool work)
{
return work;
}
}
Here is the response I get when attempting to access it via REST Console using
Request URL: http://localhost:8989/api/Settings/Test
Request Method:POST
Status Code:404 Not Found
Request Headersview source
Accept:*/*
Accept-Encoding:gzip,deflate,sdch
Accept-Language:en-US,en;q=0.8
Cache-Control:no-cache
Connection:keep-alive
Content-Length:9
Content-Type:application/x-www-form-urlencoded
Host:localhost:8989
Origin:chrome-extension://cokgbflfommojglbmbpenpphppikmonn
Pragma:no-cache
User-Agent:Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36
(KHTML, like Gecko) Chrome/31.0.1650.63 Safari/537.36
Form Dataview parsed
work=true
Response Headersview source
Content-Length:205
Content-Type:application/json; charset=utf-8
Date:Mon, 23 Dec 2013 22:41:10 GMT
Server:Microsoft-HTTPAPI/2.0
So, if I change my request to a post to the url below, it works.
http://localhost:8989/api/Settings/Test?work=true
Why isn't Content-Type:application/x-www-form-urlencoded working?
Your action method parameter is bool, which is a simple type. By default, ASP.NET Web API populates it from URI path or query string. That is the reason http://localhost:8989/api/Settings/Test?work=true works.
When you send the same in the request body, it does not work because ASP.NET Web API binds body to a complex type (class) by default and hence the body will not be bound to your argument type bool. To ask Web API to bind the simple type from body, change your action method like this.
public bool Test([FromBody]bool work)
Then, you will need to send only the value in the body, like this.
=true.
The problem is not with the Content-Type. The rest console uses AJAX and CORS, see http://en.wikipedia.org/wiki/Cross-origin_resource_sharing. This is indicated by the Origin http header in the request.
The server that contains the SettingsController must support CORS. If it doesn't the AJAX request will always return a 404.
The easiest way for you to support CORS is to always return a Access-Control-Allow-Origin: * in the request HTTP headers.

Categories

Resources