ASP.NET Web API very slow while controller code is fast - c#

asp.net .net 4.7 web API
I am accessing a ASP.NET web API via jQuery.
JQuery collects a number of ids and sends one ajax request per id.
If there is 1 id the query is processed in 6-10ms
If there is more than 1 it can take 500-1000 ms as seen in browser web dev timing or in IIS log access log files. A stopwatch in controller is consistent for all queries below 10ms.
Same issue on both IIS prod server and local Dev machine.
function dashboardLoader() {
var tcs = $(".dashboardtc");
for (var i = 0; i < tcs.length; i++) {
var fid = $(tcs[i]).attr("data-fid");
$.ajax({
url: '/ctl/dashboardfeature/' + fid,
type: 'GET',
cache: false,
contentType: "application/json",
success: function (d1) {
adjustDashBoardCell(d1);
},
error: function (result) {
console.log(result.Message);
}
});
}
}
API:
[Authorize]
[Route("ctl/dashboardfeature/{fid}")]
[HttpGet]
public async Task<dashboardFeaturePayload> GetDashboardFeature(string fid)
{
try
{
var timer = new Stopwatch();
timer.Start();
if (!User.Identity.IsAuthenticated) return null;
string un = User.Identity.Name;
int fID = fid.val();
string rtn = await Task.FromResult<string>(DashboardCode.GetDashboardFeatureAPI(fID, true));
timer.Stop();
TimeSpan timeTaken = timer.Elapsed;
System.Diagnostics.Debug.WriteLine(timer.Elapsed.TotalMilliseconds.ToString());
return new dashboardFeaturePayload(fID, rtn);
}
catch (Exception ex)
{
AdminTask.ExceptionLoging(ex);
return new dashboardFeaturePayload(fid.val(), "ERROR");
}
}
Would appear to be link to the ability of IIS to process multiple requests arriving in the same time.
Web Dev Timing:
VS Output stopwatch:

Related

Ajax method calling a Web Api who its consuming a gRPC Service (how to get a list from a db?): Errors 415 and 400

I have problems in my web console, first it gives me error 415 without contentType: 'application/json; charset=UTF-8', in ajax method and with it gives me error 400.
I also try doing debug if the error is in backend code but it never happens and it jumps off (this don't make any sense, the debugger should run...)
My goal is to return a list of users and their email, something like this
//trying to do this in my service
IEnumerable()
//class c#
ShowUsers:
(string)Username: User1
(string)Email:user1#example.com
But I'm doing this in gRPC, so I'll have a web API controller calling the gRPC method
As there is no IEnumerable in gRPC, so my proto and method look like this:
Proto:
syntax = "proto3";
option csharp_namespace = "GrpcService1.Protos";
package UserAuth;
service UserAuth {
rpc GetAllUsers(MessageRequest) returns(ListUsersResponse);
}
message ListUserResponse{
string username = 1;
string email = 2;
}
message ListUsersResponse{
repeated ListUserResponse lstUsers = 1;
}
message MessageRequest{
string message = 1;
}
gRPC method service c#
public override Task<ListUsersResponse> GetAllUsers(MessageRequest request, ServerCallContext context)
{
//This gives me an IEnumerable<ShowUsers> (this is
correct)
var a = _userBll.getAllUsers();
//Here in this lines maybe be the problem but I don't
//know where (maybe be because of json type or something into a list)
//here im trying to put separate list of users and emails
var names = a.Select(x => x.Username);
var emails = a.Select(y => y.Email);
//here im trying to put the lists into a Response
var all = new ListUserResponse
{
Username = names.ToString(),
Email = emails.ToString()
};
//the above will give the same but the
ListUsersResponse is a repeated attribute
var response = new ListUsersResponse
{
LstUsers = { all }
};
//all ok
request.Message = "Sucess";
return Task.FromResult(response);
}
The code below is correct (I test with a POST method the controller and the ajax I test without the gRPC and works fine) but you will have the idea of ​​what I'm doing (its the controller and the ajax method)
Controller [HTTPGET]:
[HttpGet("getAllUserInfo_2"), Authorize]
public async Task<ActionResult<ListUsersResponse>> GetAll_2([FromBody] MessageRequest message)
{
_logger.Log(LogLevel.Information, "Request Received for AuthController::Register");
var results = await _userClient.GetAllUsersAsync(message);
_logger.Log(LogLevel.Information, "Sending Response from AuthController::Register");
return Ok(results);
}
Ajax Method:
$(function b() {
debugger;
var $users_A = $('#users_A');
$.ajax({
contentType: 'application/json; charset=UTF-8', //if I comment this gives me 415
url: uri_3_2,
type: 'GET',
dataType: 'json',
beforeSend: function(request) {
request.setRequestHeader("Authorization", 'Bearer ' + localStorage.getItem("key"));
},
success: function(date) {
$.each(data, function (i, rice) {
$users_A.append('<li>Name: ' + arroz.username + ' Email: ' + arroz.email + ' </li>');
});
},
error: function (xhr, textStatus, errorThrown) {
console.log('XHR:' + xhr + '\nTextStatus:' + textStatus + '\nErrorThrown:' + errorThrown); //this should give me more info about the error but don't works... But it
//works fine the implementation code
$users_A.append('<h4>ERRRROORRRR</h4>');
}
});
});
Any help is welcome
A bi-directional streaming RPC would be a better option as it will
improve your performance significantly and may solve your problem.
you need to change your proto as the following:
syntax = "proto3";
option csharp_namespace = "GrpcService1.Protos";
package UserAuth;
service UserAuth {
rpc GetAllUsers(MessageRequest) returns(stream UserResponse);
}
message UserResponse{
string username = 1;
string email = 2;
}
message MessageRequest{
string message = 1;
}
gRPC method service c#
public override async Task GetAllUsers(MessageRequest request, IServerStreamWriter<UserResponse> responseStream, ServerCallContext context)
{
var users = _userBll.getAllUsers();
foreach (user in users)
{
await responseStream.WriteAsync(new UserResponse
{
Username = user.Username.ToString(),
Email = user.Email.ToString()
});
}
}
in client:
public async Task<List<UserResponse> GetAllUsers()
{
var userResponseList = new List<UserResponse>();
using var call = client.GetAllUsers(new MessageRequest());
while (await call.ResponseStream.MoveNext())
{
var userResponse = new UserResponse
{
Username = call.ResponseStream.Current.Username,
Email = call.ResponseStream.Current.Email
});
userResponseList.Add(userResponse);
}
return userResponseList;
}
the client object has come from the channel which is created from the gRPC service URL (I assume you know it).
Now you can make this a service and call it by dependency injection from your controller.
I didn't test it so it may have some compile errors but the approach is correct.

WebAPI - HTTP405: BAD METHOD - The HTTP verb used is not supported using JavaScript

I'm posting to my Web API on Azure from a remote webpage. I use the following:
$.ajax({
type: "POST",
url: BaseHref + "/api/PIRknownPersons/",
data: data,
contentType: "application/json; charset=UTF-8",
success: function (response) {
//alert('Saved known ');
},
error: function (e) {
//alert('Unable to save Known ' );
}
});
I could be wrong but it looks like remote posting is blocked on Azure as the code works fine when I do it locally. Is this the case? Is there a setting on Azure that I can turn off?
PIRknownPersons -
namespace TripWeb.Controllers
{
public class PIRknownPersonsController : ApiController
{
private TripDB3Entities db = new TripDB3Entities();
// POST: api/PIRknownPersons
[ResponseType(typeof(bool))]
public HttpResponseMessage Post([FromBody]ApiPIRknownPerson APIPIRknownPerson)
{
var inst = db.Institutions.Where(x => x.RemoteID == APIPIRknownPerson.RemoteID).FirstOrDefault();
if (inst == null)
{
return Request.CreateErrorResponse(HttpStatusCode.Unauthorized, "Action Not Authorized");
}
var newPIRknownPerson = ObjectMapper.GetEfApiPIRknownPerson(APIPIRknownPerson);
try
{
db.PIRknownPersons.Add(newPIRknownPerson);
db.SaveChanges();
return Request.CreateResponse<bool>(HttpStatusCode.OK, true);
}
catch (Exception ex)
{
return Request.CreateErrorResponse(HttpStatusCode.BadRequest, ex.Message);
}
}
}
}
I found the problem - or at least I got it working. I did two things:
Enabled CORS on Azure through the control panel
Appended [System.Web.Http.AcceptVerbs("GET", "POST")] to the top of my controller files.
Don't know if both are needed but it works now anyway.

external hyper link does not work in IIS express. How do I get hyper links to reach internet?

Ok so when you click a hyper link in my web app it just adds to the URL to the end. like so http://localhost:8080/http//:www.youtube.com. I'm using visual studio .net to make web rest APIs. The web app lunches on IIS express 10. the home page URL is http://localhost:8080/index.html.
user gets links to click
links are populated
error after user clicks link
error page
$.ajax(
{
url: "/api/Link/1",
type: "GET",
dataType: "json",
success: function (data)
{
var array = $.parseJSON(data);
$("a").remove();
for(var i=0;i<array.length;i++)
{
$("body").append("" + array[i].name + "");
}
},
error: function ()
{
}
});
//rest api called
[HttpGet]
public string GetLinkList(int id)
{
string json = "[";
using (StreamReader infile = new StreamReader("C: /Users/jkarp/Documents/visual studio 2015/Projects/Protal/Protal/App_Data/linkObjs.txt"))
{
while (!infile.EndOfStream)
json += infile.ReadLine()+",";
}
if(json.Length > 1)
{
json = json.Remove(json.Length - 1);
return json + "]";
}
return "[]";
}
Your link is malformed:
http//:www.youtube.com
The colon : is in the wrong place, it should be:
http://www.youtube.com
The link will be contained in your datasource linkObjs.txt, fix it there and the problem should go away.

JQuery only calls WCF web method a few times

I have a single Visual Studio project that has the aspx default page, some jquery scripts and the .svc WCF service file. It works fine if I connect with a browser (non debug) but when I debug in VS and hit a button that calls a web method to display the server time, it works fine the first and second times but then shows the first time again and cycles through both times. If I put a breakpoint on the web method it doesn't break after the first few times.
Is this to do with the fact that they are in the same project?
Sorry, here's the JQuery function:
function controlOutput(outputNumber, state) {
var webMethod = "ControlOutput?DeviceIPAddress=" + adamsIPAddress + "&OutputNumber=" + outputNumber + "&State=" + state + "&vSessionID=" + sessionID;
var fullCommandURL = webServiceUrl + webMethod;
jQuery.support.cors = true;
$.ajax({
type: "GET", //GET or POST or PUT or DELETE verb
url: fullCommandURL, // Location of the service
contentType: 'application/json; charset=utf-8', // content type sent to server
dataType: "json", //Expected data format from server
success: function (result) {//On Successfull service call
connectionInfo = result;
$(h2Test).text(result);
//alert(result);
},
error: function (result) {// When Service call fails
alert(result.statusText);
}
});
}
And the WCF Service code:
public string ControlOutput(string DeviceIPAddress, int OuputNumber, int State, Guid SessionID)
{
try
{
string ret = DateTime.Now.ToString("HH:mm:ss.fff");
AdamSocket adamSocket = new AdamSocket();
adamSocket.SetTimeout(1000, 1000, 1000);
bool con = adamSocket.Connect(DeviceIPAddress, ProtocolType.Tcp, 502);
bool res = adamSocket.Modbus().ForceSingleCoil(17, State);
adamSocket.Disconnect();
return ret;
}
catch (Exception ex)
{
LogEvent(null, "Application Exception", ex.Message, SessionID);
return "Failed";
}
}
It turns out the GET command was caching and so only executing a couple of times. Using POST fixed this. ;)

session timeout on ajax call

I know this is duplicate but I could not get reliable solution(for asp.net web).
I just want to redirect to the login page if session expires.
I have tried following:
1. using jquery status code
$.ajax({
type: "POST",
url: "stream.asmx/SomeMethod",
contentType: "application/json; charset=utf-8",
dataType: "json",
success: function (msg) {
//success msg
},
error: function (request, status, error) {
if (status = 403) {
location.href = 'login.aspx';
}
}
});
Problem: this returns same status code(403) for other errors too, which I only expect for session timeout.
2. Sending json message whether session expired
code behind:
if (!object.Equals(HttpContext.Current.Session["User"], null))
{
Id = int.Parse(HttpContext.Current.Session["User"].ToString());
}
else
{
result = from row in dtscrab.AsEnumerable()
select new
{
redirectUrl = "login.aspx",
isRedirect = true
};
}
on $.ajax success:
success: function (msg) {
if (msg.d[0].isRedirect) {
window.location.href = msg.d[0].redirectUrl;
}
else {
//load containt
}
}
Problem: It's somehow desn't invoke ajax success line if session expires(it does return correct json). And even this is not a proper way if I have many number of ajax request in the page(should be handled globally).
However, I saw this post which is really good soltion but it's for mvc using AuthorizeAttribute: handling-session-timeout-in-ajax-calls
So, Is there I can use same concept used in mvc using AuthorizeAttribute in asp.net web api? If not, how I can troubleshoot those issue which I'm facing (any of above two mentioned)?
A 403 status code is going to cause jQuery to call the failure method. Keep the same code behind from your second try, but move the redirect handler to the failure method instead of the success method. In the success method, treat it as you normally would.
Problem:
I had same problem in my Razor MVC Application throwing exceptions while ajax calls made when session timed out.
The way I have managed to get this issue sorted is by monitoring each ajax requests by using a simple light weight Action Method (RAZOR MVC) returning a bool variable whether the Request is Authenticated or not. Please find the code below..
Layout/Master Page / Script file:
<script>
var AuthenticationUrl = '/Home/GetRequestAuthentication';
var RedirectUrl = '/Account/Logon';
function SetAuthenticationURL(url) {
AuthenticationUrl = url;
}
function RedirectToLoginPage() {
window.location = RedirectUrl;
}
$(document).ajaxStart(function () {
$.ajax({
url: AuthenticationUrl,
type: "GET",
success: function (result) {
if (result == false) {
alert("Your Session has expired.Please wait while redirecting you to login page.");
setTimeout('RedirectToLoginPage()', 1000);
}
},
error: function (data) { debugger; }
});
})
Then in Home Controller/Server side you need a method to verify the request and return the boolean variable..
public ActionResult GetAuthentication ( )
{
return Json(Request.IsAuthenticated, JsonRequestBehavior.AllowGet);
}
This will validate each ajax request and if the session got expired for any ajax request, it will alert the user with a message and redirect the user to the login page.
I would also suggest not to use standard Alert to Alert. User some Tool tip kind of formatted div Alerts. Standard JS Alerts might force the user to click OK before redirection.
Hope it helps.. :)
Thanks,
Riyaz
Finally, I ended up following.
public class IsAuthorizedAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
if (filterContext.HttpContext.Request.IsAjaxRequest())
{
var sessions = filterContext.HttpContext.Session;
if (sessions["User"] != null)
{
return;
}
else
{
filterContext.Result = new JsonResult
{
Data = new
{
status = "401"
},
JsonRequestBehavior = JsonRequestBehavior.AllowGet
};
//xhr status code 401 to redirect
filterContext.HttpContext.Response.StatusCode = 401;
return;
}
}
var session = filterContext.HttpContext.Session;
if (session["User"] != null)
return;
//Redirect to login page.
var redirectTarget = new RouteValueDictionary { { "action", "LogOn" }, { "controller", "Account" } };
filterContext.Result = new RedirectToRouteResult(redirectTarget);
}
}
Handling client side
<script type="text/javascript">
$(document).ajaxComplete(
function (event, xhr, settings) {
if (xhr.status == 401) {
window.location.href = "/Account/LogOn";
}
});
</script>
you can set session time out expire warning some thing like ....
<script type="text/javascript">
//get a hold of the timers
var iddleTimeoutWarning = null;
var iddleTimeout = null;
//this function will automatically be called by ASP.NET AJAX when page is loaded and partial postbacks complete
function pageLoad() {
//clear out any old timers from previous postbacks
if (iddleTimeoutWarning != null)
clearTimeout(iddleTimeoutWarning);
if (iddleTimeout != null)
clearTimeout(iddleTimeout);
//read time from web.config
var millisecTimeOutWarning = <%= int.Parse(System.Configuration.ConfigurationManager.AppSettings["SessionTimeoutWarning"]) * 60 * 1000 %>;
var millisecTimeOut = <%= int.Parse(System.Configuration.ConfigurationManager.AppSettings["SessionTimeout"]) * 60 * 1000 %>;
//set a timeout to display warning if user has been inactive
iddleTimeoutWarning = setTimeout("DisplayIddleWarning()", millisecTimeOutWarning);
iddleTimeout = setTimeout("TimeoutPage()", millisecTimeOut);
}
function DisplayIddleWarning() {
alert("Your session is about to expire due to inactivity.");
}
function TimeoutPage() {
//refresh page for this sample, we could redirect to another page that has code to clear out session variables
location.reload();
}
4xx are HTTP error status codes and would cause jquery to execute the onFailure callback.
Also, beware of using 3xx for redirects when you want to process the payload. Internet Explorer, in my experience, just does a redirect (without looking at the payload) when a 3xx status code is returned.
I'd say, throw a 403 and handle the situation. To the client 403 implies the resource access is forbidden. There can be multiple reasons, which is OK I guess.
For those using a ScriptManager, you can easily check for ajax request and then redirect with the following code:
private void AjaxRedirect(string url)
{
Response.StatusCode = 200;
Response.RedirectLocation = url;
Response.Write("<html></html>");
Response.End();
}
Then check for request type and redirect accordingly (using routes here):
if (ScriptManager.GetCurrent(Page).IsInAsyncPostBack)
{
var redirectUrl = RouteTable.Routes.GetVirtualPath(null, "Default", null).VirtualPath;
AjaxRedirect(redirectUrl);
}
else
{
Response.RedirectToRoute("Default");
}
The "Default" route is a route defined in the routes collection:
routes.MapPageRouteWithName("Default", "", "~/default.aspx");
If you prefer, instead of using ScriptManager for ajax request check, you can use:
if (Request.Headers["X-Requested-With"] == "XMLHttpRequest") {
code here...
}

Categories

Resources