NotFound() returns 200 instead of 404 - c#

I've been stuck on this recently and can't figure out why this is happening.
I'm using an MVC Controller in .Net Core to return a NotFound() "404" response.
However, client side (using angular) if I console.log the response, it shows this...
status:200
statusText:"OK"
Is there any reason why returning NotFound() would return an error code of 200 instead of the intended 404?
This is my Controller GET.
// GET: api/cause/cause-name
[HttpGet("{name}")]
[AllowAnonymous]
public IActionResult GetCauseByName(string name)
{
var input = _service.GetCauseByName(name);
if (input == null)
{
return NotFound();
}
else
{
return Ok(input);
}
}
Any help would be appreciated! Thanks!
To be clear, for this instance assume input is null. What I'm testing is it hitting NotFound() not the return OK(input). Breakpoints have been set and it does hit the NotFound() but still returns the response code of 200.
Headers--
GET /cause/dsdasdas
HTTP/1.1
Host: localhost:48373
Connection: keep-alive
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/‌​webp,/;q=0.8 Accept-Encoding: gzip, deflate, sdch, br Accept-Language: en-US,en;q=0.8
HTTP/1.1
200 OK
Transfer-Encoding: chunked
Content-Type: text/html; charset=utf-8
Content-Encoding: gzip Vary:
Accept-Encoding Server: Kestrel X-SourceFiles: =?UTF-8?B?QzpcVXNlcnNcaXR0ZW1wNVxEZXNrdG9wXFByb2plY3RGdW5kQX‌​BwXHNyY1xQcm9qZWN0Rn‌​VuZFxjYXVzZVxkc2Rhc2‌​Rhcw==?= X-Powered-By: ASP.NET Date: Thu, 25 May 2017 14:51:29 GMT –
POSTMAN HEADERS
Content-Encoding →gzip
Content-Type →text/html; charset=utf-8
Date →Thu, 25 May 2017 15:18:31 GMT
Server →Kestrel
Transfer-Encoding →chunked
Vary →Accept-Encoding
X-Powered-By →ASP.NET
X-SourceFiles →=?UTF-8?B?QzpcVXNlcnNcaXR0ZW1wNVxEZXNrdG9wXFByb2plY3RGdW5kQXBwXHNyY1xQcm9qZWN0RnVuZFxjYXVzZVxkc2Rhc2Rhcw==?=

I have asked a similar question and received some kind of answer... NotFound() doesn't seem to work as expected
The solution Redirect("~/404.html"); returns 200.
However, there's another way.
// Wherever you want to return your standard 404 page
return Redirect("Home/StatusCode?code=404");
public class HomeController : Controller
{
// This method allows for other status codes as well
public IActionResult StatusCode(int? code)
{
// This method is invoked by Startup.cs >>> app.UseStatusCodePagesWithReExecute("/Home/StatusCode", "?code={0}");
if (code.HasValue)
{
// here is the trick
this.HttpContext.Response.StatusCode = code.Value;
}
//return a static file.
try
{
return File("~/" + code + ".html", "text/html");
}
catch (FileNotFoundException)
{
return Redirect("Home/StatusCode?code=404");
}
}
}
This does return 404.

Related

How do I post simple JSON data with a file upload?

I'm trying to set up a file upload request in a ServiceStack TypeScript client that also includes the month for which the file is relevant. How do I set up the request so that both come through to the server?
I've tried various changes, including manually changing headers to try to force Content-Type to be application/json, which didn't work (but I suspect would break the file upload even if it did).
Client-side API:
export const serviceApi = {
importData: (month: string, file: File) => {
var client = new JsonServiceClient("");
var request = new DTOs.ImportData();
// At this point, the month has a value
request.month = month.replace('/', '-').trim();
let formData = new FormData();
formData.append('description', file.name);
formData.append('type', 'file');
formData.append('file', file);
const promise = client.postBody(request, formData);
return from(promise);
},
};
DTO definition:
[Route("/api/data/import/{Month}", "POST")]
public class ImportData : IReturn<ImportDataResponse>
{
public string Month { get; set; }
}
public class ImportDataResponse : IHasResponseStatus
{
public ResponseStatus ResponseStatus { get; set; }
}
Server-side API:
[Authenticate]
public object Post(ImportData request)
{
if (Request.Files == null || Request.Files.Length <= 0)
{
throw new Exception("No import file was received by the server");
}
// This is always coming through as null
if (request.Month == null)
{
throw new Exception("No month was received by the server");
}
var file = (HttpFile)Request.Files[0];
var month = request.Month.Replace('-', '/');
ImportData(month, file);
return new ImportDataResponse();
}
I can see that the file is coming through correctly on the server side, and I can see an HTTP request going through with the month set in the query string parameters as "07-2019", but when I break in the server-side API function, the month property of the request is null.
Update, here are the HTTP Request/Response headers:
Request Headers
POST /json/reply/ImportData?month=07-2019 HTTP/1.1
Host: localhost:40016
Connection: keep-alive
Content-Length: 7366169
Origin: http://localhost:40016
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/74.0.3729.157 Safari/537.36
Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryI8CWlbw4tP80PkpZ
Accept: */*
Referer: http://localhost:40016/data
Accept-Encoding: gzip, deflate, br
Accept-Language: en-GB,en-US;q=0.9,en;q=0.8
Cookie: _ga=GA1.1.673673009.1532913806; ASP.NET_SessionId=gtwdk3wsvdn0yulhxyblod3g; __utmc=111872281; __utmz=111872281.1533684260.1.1.utmcsr=(direct)|utmccn=(direct)|utmcmd=(none); ss-opt=perm; __utma=111872281.673673009.1532913806.1550789161.1550794391.20; _gid=GA1.1.893581387.1558389301; ss-id=kfq4G0GYb3WldSdCaRyJ; ss-pid=aZ400sqM4n3TQgNVnHS2
Response Headers
HTTP/1.1 500 Exception
Cache-Control: private
Content-Type: application/json; charset=utf-8
Vary: Accept
Server: Microsoft-IIS/10.0
X-Powered-By: ServiceStack/5.10 NET45 Win32NT/.NET
X-AspNet-Version: 4.0.30319
X-SourceFiles: =?UTF-8?B?RTpcVEZTXFNvdXJjZVxNZWRpc2VuXFdlYnNpdGVzXE9OaWlDU1xNYWluXFNvdXJjZVxPbmlpY3NSZWFjdC1QYXltZW50c1xPbmlpY3NSZWFjdFxPbmlpY3NSZWFjdFxqc29uXHJlcGx5XEltcG9ydE1CU0NvZGVz?=
X-Powered-By: ASP.NET
Date: Tue, 21 May 2019 21:49:03 GMT
Content-Length: 605
Query String Parameters
month=07-2019
You'll be able to upload a file using JavaScript's fetch API directly, e.g:
let formData = new FormData();
formData.append('description', file.name);
formData.append('type', 'file');
formData.append('file', file);
fetch('/api/data/import/07-2019', {
method: 'POST',
body: formData
});
Otherwise if you want to use ServiceStack's TypeScript JsonServiceClient you would need to use the API that lets you post the Request DTO with a separate request body, e.g:
formData.append('month', '07-2019');
client.postBody(new ImportData(), formData);
I don't think the month should be part of the request header, that's kinda unorthodox. It should be part of the form data.
If you did:
formData.append('Month', month.replace('/', '-').trim());
client side, then request.Month or request.content.Month should work, depending on how the request object is handled in your instance.

SignalR - Works when deployed to Server, but stops after a few hours (MVC)

EDIT: Look at the bottom of this post for updates.
My SignalR implementation works perfectly on my local system. But when I deployed it out to my server it doesnt seem to work. Its an MVC project.
My signalR jQuery is as follows:
var clientHub = $.connection.gamehub;
$(function () {
var signalRHubInitialized = false;
var image = $("#Ico");
var count = 0;
initializeSignalRHubStore();
function initializeSignalRHubStore() {
if (signalRHubInitialized)
return;
try {
clientHub.client.broadcastMessage = function (message) {
if (message === "Refresh")
reloadIndexPartial();
};
$.connection.hub.start().done(function () {
clientHub.server.initialize($("#NotifierEntity").val());
signalRHubInitialized = true;
});
} catch (err) {
signalRHubInitialized = false;
}
};
function reloadIndexPartial() {
//$.post('#(Url.Action("LivePartial", "Scrim", null, Request.Url.Scheme))')
var id = $("#SeriesDetail_Id").val();
$.post('/Scrim/LivePartial/' + id)
.done(function (response) {
try {
count = count + 1;
var favicon = new Favico({
animation: 'pop',
image: image
});
favicon.badge(count);
}
catch (exception) {
}
$("#summary-wrapper").html("");
$("#summary-wrapper").html(response);
if (!signalRHubInitialized)
initializeSignalRHubStore();
});
};
});
I downloaded Fiddler to see what was going on:
/signalr/hubs returned a HTTP200
GET http://sitename.com/signalr/hubs HTTP/1.1
Host: sitename.com
Connection: keep-alive
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.113 Safari/537.36
Accept: */*
Referer: http://sitename.com/scrim/Live/2835
Accept-Encoding: gzip, deflate
Accept-Language: en-US,en;q=0.8
Cookie: _gat=1; _ga=GA1.2.1342148401.1475084375; _gid=GA1.2.2092796788.1503865866
negotiate returned at HTTP200
GET http://sitename.com/signalr/negotiate?clientProtocol=1.5&connectionData=%5B%7B%22name%22%3A%22gamehub%22%7D%5D&_=1505151041506 HTTP/1.1
Host: sitename.com
Connection: keep-alive
Accept: text/plain, */*; q=0.01
X-Requested-With: XMLHttpRequest
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.113 Safari/537.36
Content-Type: application/json; charset=UTF-8
Referer: http://sitename.com/scrim/Live/2835
Accept-Encoding: gzip, deflate
Accept-Language: en-US,en;q=0.8
Cookie: _gat=1; _ga=GA1.2.1342148401.1475084375; _gid=GA1.2.2092796788.1503865866
connect, didnt return anything
GET http://sitename.com/signalr/connect?transport=serverSentEvents&clientProtocol=1.5&connectionToken=S8rqz2NPvVSJxbS1%2FpLm7yHTinGHWK1SnAwh8IfYA%2BP7nVb9RV%2FJzSFsf8Q%2BTv6Z%2Fae%2FIoZKlHKyeTxaEn3obg%2FVViYTB5HZxnrvKvtBZtQopvGPdj1i4o8Z9wGlCz3%2F&connectionData=%5B%7B%22name%22%3A%22gamehub%22%7D%5D&tid=10 HTTP/1.1
Host: sitename.com
Connection: keep-alive
Accept: text/event-stream
Cache-Control: no-cache
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.113 Safari/537.36
Referer: http://sitename.com/scrim/Live/2835
Accept-Encoding: gzip, deflate
Accept-Language: en-US,en;q=0.8
Cookie: _gat=1; _ga=GA1.2.1342148401.1475084375; _gid=GA1.2.2092796788.1503865866
start returned a HTTP200
GET http://sitename.com/signalr/start?transport=serverSentEvents&clientProtocol=1.5&connectionToken=S8rqz2NPvVSJxbS1%2FpLm7yHTinGHWK1SnAwh8IfYA%2BP7nVb9RV%2FJzSFsf8Q%2BTv6Z%2Fae%2FIoZKlHKyeTxaEn3obg%2FVViYTB5HZxnrvKvtBZtQopvGPdj1i4o8Z9wGlCz3%2F&connectionData=%5B%7B%22name%22%3A%22gamehub%22%7D%5D&_=1505151041507 HTTP/1.1
Host: sitename.com
Connection: keep-alive
Accept: text/plain, */*; q=0.01
X-Requested-With: XMLHttpRequest
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.113 Safari/537.36
Content-Type: application/json; charset=UTF-8
Referer: http://sitename.com/scrim/Live/2835
Accept-Encoding: gzip, deflate
Accept-Language: en-US,en;q=0.8
Cookie: _gat=1; _ga=GA1.2.1342148401.1475084375; _gid=GA1.2.2092796788.1503865866
Send returned a HTTP200
POST http://sitename.com/signalr/send?transport=serverSentEvents&clientProtocol=1.5&connectionToken=S8rqz2NPvVSJxbS1%2FpLm7yHTinGHWK1SnAwh8IfYA%2BP7nVb9RV%2FJzSFsf8Q%2BTv6Z%2Fae%2FIoZKlHKyeTxaEn3obg%2FVViYTB5HZxnrvKvtBZtQopvGPdj1i4o8Z9wGlCz3%2F&connectionData=%5B%7B%22name%22%3A%22gamehub%22%7D%5D HTTP/1.1
Host: sitename.com
Connection: keep-alive
Content-Length: 2227
Accept: text/plain, */*; q=0.01
Origin: http://sitename.com
X-Requested-With: XMLHttpRequest
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.113 Safari/537.36
Content-Type: application/x-www-form-urlencoded; charset=UTF-8
Referer: http://sitename.com/scrim/Live/2835
Accept-Encoding: gzip, deflate
Accept-Language: en-US,en;q=0.8
Cookie: _gat=1; _ga=GA1.2.1342148401.1475084375; _gid=GA1.2.2092796788.1503865866
data=%7B%22H%22%3A%22gamehub%22%2C%22M%22%3A%22Initialize%22%2C%22A%22%3A%5B%22%7B%5C%22SqlQuery%5C%22%3A%5C%22SELECT+%5C%5Cr%5C%5Cn++++%5BExtent1%5D.%5BId%5D+AS+%5BId%5D%2C+%5C%5Cr%5C%5Cn++++%5BExtent1%5D.%5BGameGuid%5D+AS+%5BGameGuid%5D%2C+%5C%5Cr%5C%5Cn++++%5BExtent1%5D.%5BDate%5D+AS+%5BDate%5D%2C+%5C%5Cr%5C%5Cn++++%5BExtent1%5D.%5BTeamOneScore%5D+AS+%5BTeamOneScore%5D%2C+%5C%5Cr%5C%5Cn++++%5BExtent1%5D.%5BTeamZeroScore%5D+AS+%5BTeamZeroScore%5D%2C+%5C%5Cr%5C%5Cn++++%5BExtent1%5D.%5BTeamOneId%5D+AS+%5BTeamOneId%5D%2C+%5C%5Cr%5C%5Cn++++%5BExtent1%5D.%5BTeamZeroId%5D+AS+%5BTeamZeroId%5D%2C+%5C%5Cr%5C%5Cn++++%5BExtent1%5D.%5BGameVariantId%5D+AS+%5BGameVariantId%5D%2C+%5C%5Cr%5C%5Cn++++%5BExtent1%5D.%5BMapId%5D+AS+%5BMapId%5D%2C+%5C%5Cr%5C%5Cn++++%5BExtent1%5D.%5BDuration%5D+AS+%5BDuration%5D%2C+%5C%5Cr%5C%5Cn++++%5BExtent1%5D.%5BSeriesId%5D+AS+%5BSeriesId%5D%2C+%5C%5Cr%5C%5Cn++++%5BExtent1%5D.%5BResult%5D+AS+%5BResult%5D%2C+%5C%5Cr%5C%5Cn++++%5BExtent1%5D.%5BActive%5D+AS+%5BActive%5D%5C%5Cr%5C%5Cn++++FROM+%5Bdbo%5D.%5BGame%5D+AS+%5BExtent1%5D%5C%5Cr%5C%5Cn++++WHERE+(%5BExtent1%5D.%5BActive%5D+%3D+1)+AND+(%5BExtent1%5D.%5BSeriesId%5D+%3D+%40p__linq__0)%5C%22%2C%5C%22SqlConnectionString%5C%22%3A%5C%22Data+Source%3DWIN-1J1JAEOEU33%3BInitial+Catalog%3DSiteName%3BIntegrated+Security%3DTrue%3BMultipleActiveResultSets%3DTrue%3B%5C%22%2C%5C%22SqlParameters%5C%22%3A%5B%7B%5C%22CompareInfo%5C%22%3A0%2C%5C%22XmlSchemaCollectionDatabase%5C%22%3A%5C%22%5C%22%2C%5C%22XmlSchemaCollectionOwningSchema%5C%22%3A%5C%22%5C%22%2C%5C%22XmlSchemaCollectionName%5C%22%3A%5C%22%5C%22%2C%5C%22DbType%5C%22%3A11%2C%5C%22LocaleId%5C%22%3A0%2C%5C%22ParameterName%5C%22%3A%5C%22p__linq__0%5C%22%2C%5C%22Precision%5C%22%3A0%2C%5C%22Scale%5C%22%3A0%2C%5C%22SqlDbType%5C%22%3A8%2C%5C%22SqlValue%5C%22%3A%7B%5C%22IsNull%5C%22%3Afalse%2C%5C%22Value%5C%22%3A2835%7D%2C%5C%22UdtTypeName%5C%22%3A%5C%22%5C%22%2C%5C%22TypeName%5C%22%3A%5C%22%5C%22%2C%5C%22Value%5C%22%3A2835%2C%5C%22Direction%5C%22%3A1%2C%5C%22IsNullable%5C%22%3Afalse%2C%5C%22Offset%5C%22%3A0%2C%5C%22Size%5C%22%3A0%2C%5C%22SourceColumn%5C%22%3A%5C%22%5C%22%2C%5C%22SourceColumnNullMapping%5C%22%3Afalse%2C%5C%22SourceVersion%5C%22%3A512%7D%5D%7D%22%5D%2C%22I%22%3A0%7D
I have added this to my web config:
<modules runAllManagedModulesForAllRequests="true"></modules>
Looking through all the responses it seems that everything is working correctly but the page Im on is not being updated when a new entry has been added to the database.
On my local development system my project is set up using IIS it works flawlessly.
Could anyone point me in the right direction please.
EDIT: I have got it working on the server now. But it seems that it works right after it has been deployed for a few hours. Then after that it seems to stop working. So I have to assume that the signalr connection is being disposed at some stage and now getting reinstated?
Here is my RegisterServices class:
private static IContainer RegisterServices(ContainerBuilder builder)
{
builder.RegisterControllers(Assembly.GetExecutingAssembly());
builder.RegisterApiControllers(Assembly.GetExecutingAssembly());
builder.RegisterType<ContextEntities>()
.As<DbContext>()
.InstancePerRequest();
builder.RegisterType<DbFactory>()
.As<IDbFactory>()
.InstancePerRequest();
builder.RegisterType<UnitOfWork>()
.As<IUnitOfWork>()
.InstancePerRequest();
// Services
builder.RegisterType<MembershipService>()
.As<IMembershipService>()
.InstancePerRequest();
builder.RegisterType<CacheService>()
.As<ICacheService>()
.InstancePerRequest();
builder.RegisterType<GameHub>().ExternallyOwned();
Container = builder.Build();
return Container;
}
Here is a page where signalr is used: http://halodatahive.com/Scrim/Live/2845
I seem to be losing reference to the signalr connection after a few hours after a deployment.
EDIT: If I recycle my APP POOL the page with signalR starts working again.
This is what I ended up using to resolve the issue. It seems that after around 1 hour it was getting disconnected some how. I put this code in a few hours ago and it still seems to be working. Thanks to #Noren for all their help in chat earlier!
EDIT: This did not seem to solve the problem unfortunately.
$.connection.hub.disconnected(function() {
setTimeout(function() {
$.connection.hub.start();
}, 5000); // Restart connection after 5 seconds.
});
EDIT: Thought I would give an update as to how I got this working. Instead of using SqlDependency to trigger the SignalR I just called Clients.All.broadcastMessage("Refresh"); on the scheduled task I have running on the server when _unitOfWork.Commit() was called.
Something was causing SqlDependency to stop working and the only way to get it to pick it up again was to recycle the app pool.
I've seen something like this before. In my case it was RabbitMQ events that were lost because IIS was spinning down the application.
Is your application is not being hit very frequently? IIS has a tendency to spin down applications that it doesn't think it needs in order to save resources. That might be why it only stops working after a few hours and you can recycle to bring it back up.
See this answer.
first add Hubs folder and NotificationsHubs.cs in root
in NotificationsHubs.cs
[HubName("NotificationsHubs")]
public class NotificationsHubs : Hub
{
public static Thread NotificationsThread;
public void Send(string token, string UserAgent, string IP)
{
var serverVars = Context.Request.GetHttpContext().Request.ServerVariables;
string SignalRIp = serverVars["REMOTE_ADDR"];
string T = Context.Request.Headers["User-Agent"].ToLower();
if ((T == cryptClass.crypt.Decrypt(UserAgent)) && (SignalRIp == cryptClass.crypt.Decrypt(IP)))
{
var connection = SignalRConnections.Connections.SingleOrDefault(c => c.Token == Guid.Parse(token));
if (connection != null)
{
connection.Context = this.Context;
}
if (NotificationsThread == null || !NotificationsThread.IsAlive)
{
NotificationsThread = new Thread(new ThreadStart(NotificationsCheck));
NotificationsThread.Start();
}
}
NotificationsCheck is custom function
in NotificationController
public ActionResult Notifications()
{
NotificationsModule.messageBL = _messageBL;
long UserID = GetCurrentUser();
Notification _Notification = new Notification();
_Notification.GetToken = SignalRConnections.GetToken(UserID);
_Notification.UserAgent = cryptClass.crypt.Encrypt(Request.UserAgent.ToLower());
_Notification.IP = cryptClass.crypt.Encrypt(Request.UserHostAddress);
return View(_Notification);
}
in Notifications.cshtml view
add this JS file
<script src="~/Scripts/jquery.signalR-2.2.1.js")"></script>
<script src="~/signalr/hubs"></script>
$(function () {
// Reference the auto-generated proxy for the hub.
var chat = $.connection.NotificationsHubs;
// Create a function that the hub can call back to display messages.
chat.client.addNewMessageToPage = function (Title, Body, Icon) {
// Add the message to the page.
notifyMe(Title, Body, Icon);
};
$.connection.hub.start().done(function () {
chat.server.send('#Model.GetToken', '#Model.UserAgent', '#Model.IP');
$('#sendmessage').click(function () {
// Call the Send method on the hub.
chat.server.send();
});
});
});
notifyMe(Title, Body, Icon); is jquery custom function

/POST a gzipped body to an ASP.NET Web API application host

Problem:
Web API 2.2 Controller won't deserialize a POST body that is gzipped; deserializes without issue if the body is not Encoded.
Question
Why will my DelegatingHandler not pick up when a POST body is gzipped
Anecdote:
Let's say I have 2 services (A and B) that represent a sequence.
+GET/A returns A'
+POST/B requires A' and returns B'
A.cs
public class AController : ApiController
{
[HttpGet]
[Route("A")]
public HttpResponseMessage GetA()
{
APrime _aprime = new APrime("A'");
return Request.CreateResponse(HttpStatusCode.OK, new[]{_aprime});
}
}
B.cs
public class BController : ApiController
{
[HttpPost]
[Route("B")]
public HttpResponseMessage PostAReturnB([FromBody]IEnumerable<APrime> aprime)
{
if(aprime == null)return Request.CreateResponse(HttpStatusCode.BadRequest, "aprime required");
BPrime _bprime = new BPrime("B'");
return Request.CreateResponse(HttpStatusCode.OK, new[]{_bprime});
}
}
No Delegating Handler
Unencoded Communication
/GET+http://localhost:8880/A
Accept: application/json
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
Content-Length: 20257
[{...A'...}]
/POST+http://localhost:8880/B
Accept: application/json
Content-Type: application/json
Content-Length: 20257
[{...A'...}]
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
Content-Length: 59466
[{...B'...}]
Encoded Communication
/GET+http://localhost:8880/A
Accept: application/json
Accept-Encoding: gzip
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
Content-Length: 4000
<binary data representing a gzip encoded "A'">
/POST+http://localhost:8880/B
Accept: application/json
Content-Type: application/json
Content-Length: 4000
Content-Encoding: gzip
<binary data representing a gzip encoded "A'">
HTTP/1.1 400 Bad Request
Content-Type: application/json; charset=utf-8
Content-Length: 15
aprime required
With a Delegating Handler
public class MyRequestHandler : DelegatingHandler
{
protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
if (request.Method == HttpMethod.Post)
{
//Do Stuff that checks if the body is compressed.
//Decompress it
}
return base.SendAsync(request, cancellationToken);
}
}
Unencoded Communication
Handler is invoked, service fires normally
Encoded Communication
Handler never fires. Fiddler displays a 504
I thought I had an A-Ha when it finally dawned on me that I was passing my Response bodies through my clipboard, so I tried to implement a custom rule in Fiddler to perform the gzip compression on the clear body content.
if(oSession.requestBodyBytes != null && (oSession.oRequest.headers.Exists("Client.Request-Encoding")))
{
oSession["ui-italics"] = "true";
if(oSession.oRequest["Client.Request-Encoding"] == "gzip")
{
oSession.requestBodyBytes = Utilities.GzipCompress(oSession.requestBodyBytes);
oSession["Content-Length"] = oSession.requestBodyBytes.Length.ToString();
oSession.oRequest["Client.CustomRule"] = "[Fiddler][Custom Rule.GZip]";
oSession["ui-color"] = "green";
oSession["ui-italics"] = "false";
oSession["ui-bold"] = "true";
}else{
oSession["ui-color"] = "crimson";
oSession["ui-italics"] = "true";
oSession.oRequest["Client.Request-Encoding.Error"] = "Rule not processed. gzip is the only supported Request-Encoding value";
}
}
My requests that process through this rule just go off into the nether.

Kendo Scheduler - Read datasource - Post array of strings to Web API

I am trying to accomplish following, get appointments of a user through POST request as I need to post other calendar ids to get appointments of other users as well. The POST request is sent to a Web API. The endpoint gets hit but the array of calendarIds is always empty.
This is the datasource definition:
dataSource: new kendo.data.SchedulerDataSource({
batch: true,
transport: {
read: {
url: "/api/MyCalendar/GetAppointments",
dataType: "json",
type: "POST"
},
parameterMap: function(data, type) {
if (type === "read") {
return JSON.stringify(data);
}
}
}
This is the Web API implementation:
[HttpPost]
public HttpResponseMessage GetAppointments(string[] calendarIds)
{
// calendarIds is always empty
This the request posted content (textView) from fiddler:
{"calendarIds":["1c78e75f-9516-42cf-a439-271ee997abf1"]}
I am not sure what is wrong in here, thanks for any help on this.
Update:
The whole Raw request:
POST http://xxxxx/api/MyCalendar/GetAppointments HTTP/1.1
Host: ccmspatientmanager
Connection: keep-alive
Content-Length: 56
Accept: application/json, text/javascript, */*; q=0.01
Origin: http://xxxxx
X-Requested-With: XMLHttpRequest
User-Agent: Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/34.0.1847.116 Safari/537.36
Content-Type: application/x-www-form-urlencoded; charset=UTF-8
Referer: http://xxxxx/Home/MyCalendar
Accept-Encoding: gzip,deflate,sdch
Accept-Language: cs,en-GB;q=0.8,en;q=0.6,de-DE;q=0.4,de;q=0.2,sk;q=0.2
Cookie: ASP.NET_SessionId=flcab5ecct1zaopgqmpz0rhg; .ASPXAUTH=DAED17623F4B0E8F4AB0C3176EC0B73DD29A65650E93DB9664D52C9D23D34C52F1B312923B0A5F8A0D66DAF5C72864BF5827CC667D181DDE5EBC43C651D3C41FBFF315884DD74272E74E4A08D0D2380696B1C5B6
{"calendarIds":["1c78e75f-9516-42cf-a439-271ee997abf1"]}
You may try annotating your WebAPI post method with [FromBody] attribute
[HttpPost]
public HttpResponseMessage GetAppointments([FromBody]string[] calendarIds)
Also make sure you are passing in an Array in the request body instead of an Object.
What you are sending right now {"calendarIds":["1c78e75f-9516-42cf-a439-271ee997abf1"]} is an object whereas the WebAPI method accepts an Array
You can try:
parameterMap: function(data, type) {
if (type === "read") {
var values = data.calendarIds.split(','),
return JSON.stringify(values);
}
}

Google chrome losing MVC Auth Cookie (Set-Cookie directive) in subsequent calls

I'm using ASP.net Core 1, MVC 6. I am using SignInManager and UserManager, to authenticate a user in a web api application (MVC6 / C#) from another MVC application (the web api Logon method is actually called from a Jquery Ajax request).
In IE, I call the Login method and when successful, it gives me a Set-Cookie response with an ASP.net auth cookie. I can then see subsequent requests have the ASP.net auth cookie attached.
In chrome, the Set-Cookie directive is returned in the response, but subsequent requests do not have the cookie attached.
Why is this happening?
The only difference I can see is that in Chrome, there is a pre-flight OPTIONS request being sent, but I have handled that in the startup.cs file in the web api and am essentially ignoring it.
Internet Explorer
My request to Login web api looks like this:
Accept */*
Accept-Encoding gzip, deflate
Accept-Language en-IE
Cache-Control no-cache
Connection Keep-Alive
Content-Length 246
Content-Type application/x-www-form-urlencoded; charset=UTF-8
Cookie BeaeN4tYO5M=CfDJ8KMNkK4F2ylMlo1LFxNzxWLNDECVWfhxBYRQrw_MkNQBrVIwfO6FoMIMqg1PP-nZa8Dhp3IV1ZS1uXKpknUDYegiMlEvFaNG-wqUXErvQ5wkMMc_HBI88j-7bCbD2Q7P_B6fEQOQSTKHoL5sTcH0MoM
DNT 1
Host localhost:44338
Referer https://localhost:44356/
Request POST /api/account/Login HTTP/1.1
User-Agent Mozilla/5.0 (Windows NT 6.3; WOW64; Trident/7.0; rv:11.0) like Gecko
X-ACL-Key 4A6F0007-95E7-4423-B786-6FBF981799FE
Response like this:
Response HTTP/1.1 200 OK
Cache-Control no-cache
Pragma no-cache
Content-Type application/json; charset=utf-8
Expires -1
Server Kestrel
Set-Cookie oAuthInterop=CfDJ8Asqo6qO2cNHlXpsdNLsuoQWhLxXcnaNkAMTB-VvpkMRIz2AiM_7feoIM29gza_zZz97qaE6TKdqK8y1jDPjDDyiiMdOMiuCmCoV5X4IQ9xtHvpGgmFoxOSiYFVeVOBbHsLx4BccL647F9sJ07M55zvjMx_7wrt32omhONH64vmc12P3nepwZjNSIFYfom1U0Z4r4EX_0tZjKRH7FrdvO0PI2iY5SMaKhCcBw1QXpQHSUxL6Hm-Wr8Q46gFAYoa6YffJV0Rx80FvJHmr1LMAA6PAF0dU_DzNdRVHdXm14t_nbfl-6xb6o7WQN259moUhkT1ZQ9CZsYwWvn7VBmpjfIXNJvIu0FDnRaHnNMrj3uN77_cAMdO3OcyCuy-CAKJ9c-0PxKToStb9juGSNa9ClpVQPADzpUxFqxZU029AXBPavXQK2Ezvy7YT4FwCkL8TEf5AnB5hfOZ5YCBlqD30n2heMdHDbXRHpxeaQB4aoY_6uSpJ3cPazBDsbvGi4fV2-0g5NvoTGgJUXa5p4UntRmuiJ2tZHbMmEjXzf-GV6QtTFIhseKsS3n6TMX68yqQOhYOzxvHdJXPjYxvjmm6-vJw5w2FDgiEXoQJQ7qaSmGzRwOA_cE4VBV_RhzrZELmp3A; path=/; secure; httponly
X-SourceFiles =?UTF-8?B?QzpcVXNlcnNcUm9iZXJ0XERlc2t0b3BcSEJFIE1hbmFnZXJcTUFJTlxCbHVlem9uZSBXZWJBcGlcc3JjXEJ6LkFwcGxpY2F0aW9uXEJ6LkFwcGxpY2F0aW9uLkFwaVx3d3dyb290XGFwaVxhY2NvdW50XExvZ2lu?=
X-Powered-By ASP.NET
Access-Control-Allow-Methods GET,PUT,POST,DELETE
Access-Control-Allow-Headers Content-Type,x-xsrf-token,X-ACL-Key
Date Fri, 06 May 2016 14:23:22 GMT
Content-Length 16
Subsequent test web api call (IsLoggedIn):
Request GET /api/account/IsLoggedIn HTTP/1.1
X-ACL-Key 4A6F0007-95E7-4423-B786-6FBF981799FE
Accept */*
Referer https://localhost:44356/
Accept-Language en-IE
Accept-Encoding gzip, deflate
User-Agent Mozilla/5.0 (Windows NT 6.3; WOW64; Trident/7.0; rv:11.0) like Gecko
Host localhost:44338
DNT 1
Connection Keep-Alive
Cache-Control no-cache
Cookie BeaeN4tYO5M=CfDJ8KMNkK4F2ylMlo1LFxNzxWLNDECVWfhxBYRQrw_MkNQBrVIwfO6FoMIMqg1PP-nZa8Dhp3IV1ZS1uXKpknUDYegiMlEvFaNG-wqUXErvQ5wkMMc_HBI88j-7bCbD2Q7P_B6fEQOQSTKHoL5sTcH0MoM; oAuthInterop=CfDJ8Asqo6qO2cNHlXpsdNLsuoQWhLxXcnaNkAMTB-VvpkMRIz2AiM_7feoIM29gza_zZz97qaE6TKdqK8y1jDPjDDyiiMdOMiuCmCoV5X4IQ9xtHvpGgmFoxOSiYFVeVOBbHsLx4BccL647F9sJ07M55zvjMx_7wrt32omhONH64vmc12P3nepwZjNSIFYfom1U0Z4r4EX_0tZjKRH7FrdvO0PI2iY5SMaKhCcBw1QXpQHSUxL6Hm-Wr8Q46gFAYoa6YffJV0Rx80FvJHmr1LMAA6PAF0dU_DzNdRVHdXm14t_nbfl-6xb6o7WQN259moUhkT1ZQ9CZsYwWvn7VBmpjfIXNJvIu0FDnRaHnNMrj3uN77_cAMdO3OcyCuy-CAKJ9c-0PxKToStb9juGSNa9ClpVQPADzpUxFqxZU029AXBPavXQK2Ezvy7YT4FwCkL8TEf5AnB5hfOZ5YCBlqD30n2heMdHDbXRHpxeaQB4aoY_6uSpJ3cPazBDsbvGi4fV2-0g5NvoTGgJUXa5p4UntRmuiJ2tZHbMmEjXzf-GV6QtTFIhseKsS3n6TMX68yqQOhYOzxvHdJXPjYxvjmm6-vJw5w2FDgiEXoQJQ7qaSmGzRwOA_cE4VBV_RhzrZELmp3A
Response like this:
Response HTTP/1.1 200 OK
Content-Type application/json; charset=utf-8
Server Kestrel
X-SourceFiles =?UTF-8?B?QzpcVXNlcnNcUm9iZXJ0XERlc2t0b3BcSEJFIE1hbmFnZXJcTUFJTlxCbHVlem9uZSBXZWJBcGlcc3JjXEJ6LkFwcGxpY2F0aW9uXEJ6LkFwcGxpY2F0aW9uLkFwaVx3d3dyb290XGFwaVxhY2NvdW50XElzTG9nZ2VkSW4=?=
X-Powered-By ASP.NET
Access-Control-Allow-Methods GET,PUT,POST,DELETE
Access-Control-Allow-Headers Content-Type,x-xsrf-token,X-ACL-Key
Date Fri, 06 May 2016 14:23:22 GMT
Content-Length 68
CHROME
My request to Login web api looks like this:
POST /api/account/Login HTTP/1.1
Host: localhost:44338
Connection: keep-alive
Content-Length: 246
Accept: */*
Origin: https://localhost:44356
Content-Type: application/x-www-form-urlencoded; charset=UTF-8
User-Agent: Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/50.0.2661.94 Safari/537.36
X-ACL-Key: 4A6F0007-95E7-4423-B786-6FBF981799FE
Referer: https://localhost:44356/
Accept-Encoding: gzip, deflate
Accept-Language: en-GB,en-US;q=0.8,en;q=0.6
Response like this:
HTTP/1.1 200 OK
Cache-Control: no-cache
Pragma: no-cache
Content-Type: application/json; charset=utf-8
Expires: -1
Vary: Origin
Server: Kestrel
Set-Cookie: oAuthInterop=CfDJ8Asqo6qO2cNHlXpsdNLsuoRvlRjfUBWrkt3W3NzBJIoFYA6DcQivnfYmZV2O5xuiqpd75oRjZ-JeHBcjiOK0HoFJQ9f61RyJ2HDeuCNmQk0H-pA3Lzs5ft_F49dpQt0kFn3_-FzEh5-NScCbY4N6TiuYlWY4VSoKsdJJ91k7Z4LQO-0Wm3cZ6HfX0E6pLzGG4lWaZGuV-gOsVCRygR5nv_O_YpWwfaLsT_51aX6fNXVSotU6MECEkFdfWseqOGyYVj7KJrxY2mPwksE0XGACs12TnmfJzCABrzd06FnTPy3RuqJF2IWOobX6ZAHGMoAVFR07mhy9gMPyaHQ12RKmhBhZSXE-Yi3BHow2ER9d2Niligx7JjwYR7UfHFHWJdoYzewLRkZZGE5pw67O710hYyA2UCM2ODB9l9x-WDQ1A_3xjxu2Mrkp0lrF0V-h3y6V2gzEP9RyQAjDISEEZQqvb-GzfZrsRzzQcMn0TMhq5_LUKkX3AScSGRiarBzZ2O9Af3jzwTmN1BciJknJwMKRefq_zrXH7kymCD1kJM89aGkswqp2bycMQjlsjqg5k8EEhv8u1kLA7hA9NyE2ZaamB1PAWYz4NXi3Agccgw83nFi4bs6VE8ZLnyZFEwxdyEGyvQ; path=/; secure; httponly
Access-Control-Allow-Origin: https://localhost:44356
Access-Control-Allow-Credentials: true
X-SourceFiles: =?UTF-8?B?QzpcVXNlcnNcUm9iZXJ0XERlc2t0b3BcSEJFIE1hbmFnZXJcTUFJTlxCbHVlem9uZSBXZWJBcGlcc3JjXEJ6LkFwcGxpY2F0aW9uXEJ6LkFwcGxpY2F0aW9uLkFwaVx3d3dyb290XGFwaVxhY2NvdW50XExvZ2lu?=
X-Powered-By: ASP.NET
Access-Control-Allow-Methods: GET,PUT,POST,DELETE
Access-Control-Allow-Headers: Content-Type,x-xsrf-token,X-ACL-Key
Date: Fri, 06 May 2016 12:59:36 GMT
Content-Length: 16
Subsequent test web api call (IsLoggedIn):
GET /api/account/IsLoggedIn HTTP/1.1
Host: localhost:44338
Connection: keep-alive
Accept: */*
Origin: https://localhost:44356
User-Agent: Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/50.0.2661.94 Safari/537.36
X-ACL-Key: 4A6F0007-95E7-4423-B786-6FBF981799FE
Referer: https://localhost:44356/
Accept-Encoding: gzip, deflate, sdch
Accept-Language: en-GB,en-US;q=0.8,en;q=0.6
Response like this:
HTTP/1.1 401 Unauthorized
Content-Length: 0
Content-Type: text/plain; charset=utf-8
Server: Kestrel
X-SourceFiles: =?UTF-8?B?QzpcVXNlcnNcUm9iZXJ0XERlc2t0b3BcSEJFIE1hbmFnZXJcTUFJTlxCbHVlem9uZSBXZWJBcGlcc3JjXEJ6LkFwcGxpY2F0aW9uXEJ6LkFwcGxpY2F0aW9uLkFwaVx3d3dyb290XGFwaVxhY2NvdW50XElzTG9nZ2VkSW4=?=
X-Powered-By: ASP.NET
Access-Control-Allow-Methods: GET,PUT,POST,DELETE
Access-Control-Allow-Headers: Content-Type,x-xsrf-token,X-ACL-Key
Date: Fri, 06 May 2016 12:59:43 GMT
My web api controller code looks like this:
[Authorize]
[EnableCors("AllowAll")]
[Route("api/[controller]")]
public class AccountController : Controller
{
private readonly UserManager<ApplicationUser> _userManager;
private readonly SignInManager<ApplicationUser> _signInManager;
public AccountController(UserManager<ApplicationUser> userManager, SignInManager<ApplicationUser> signInManager)
{
_userManager = userManager;
_signInManager = signInManager;
}
[HttpPost("login")]
[AllowAnonymous]
public async Task<IActionResult> Login(UserLogin model)
{
if (ModelState.IsValid) {
var result = await _signInManager.PasswordSignInAsync(model.Email, model.Password, model.RememberMe, lockoutOnFailure: false);
if (result.Succeeded) {
return Json(new { success = true });
}
if (result.RequiresTwoFactor) {
return Json(new { success = false, errType = 1 });
}
if (result.IsLockedOut) {
return Json(new { success = false, errType = 2 });
} else {
ModelState.AddModelError(string.Empty, "Invalid login attempt.");
return Json(new { success = false, errType = 3 });
}
}
return Json(new { success = false, errType = 0 });
}
[HttpGet("IsLoggedIn")]
public IActionResult IsLoggedIn()
{
return Json(new {
loggedon = (HttpContext.User.Identity.Name != null && HttpContext.User.Identity.IsAuthenticated),
isauthenticated = HttpContext.User.Identity.IsAuthenticated,
username = HttpContext.User.Identity.Name
});
}
}
Startup.cs for my web api looks like this:
public class Startup
{
public static int SessionLength { get; private set; }
private string Connection;
public Startup(IHostingEnvironment env)
{
// Set up configuration sources.
var builder = new ConfigurationBuilder()
.AddJsonFile("appsettings.json")
.AddEnvironmentVariables();
Configuration = builder.Build();
SessionLength = 30;
}
public IConfigurationRoot Configuration { get; set; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
// Get the configured connection string.
Connection = Configuration["Data:DefaultConnection:ConnectionString"];
var userStore = new CustomUserStore();
var roleStore = new CustomRoleStore();
var userPrincipalFactory = new CustomUserPrincipalFactory();
services.AddInstance<IUserStore<ApplicationUser>>(userStore);
services.AddInstance<IRoleStore<ApplicationRole>>(roleStore);
services.AddInstance<IUserClaimsPrincipalFactory<ApplicationUser>>(userPrincipalFactory);
services.AddIdentity<ApplicationUser, ApplicationRole>(options => {
options.Cookies.ApplicationCookie.Events = new CookieAuthenticationEvents() {
OnRedirectToAccessDenied = ctx =>
{
if (ctx.Response.StatusCode == (int)HttpStatusCode.Unauthorized || ctx.Response.StatusCode == (int)HttpStatusCode.Forbidden) {
return Task.FromResult<object>(null);
}
ctx.Response.Redirect(ctx.RedirectUri);
return Task.FromResult<object>(null);
},
OnRedirectToLogin = ctx =>
{
if (ctx.Response.StatusCode == (int)HttpStatusCode.Unauthorized || ctx.Response.StatusCode == (int)HttpStatusCode.Forbidden) {
return Task.FromResult<object>(null);
}
ctx.Response.Redirect(ctx.RedirectUri);
return Task.FromResult<object>(null);
}
};
//options.Cookies.ApplicationCookie.CookieHttpOnly = false;
options.Cookies.ApplicationCookieAuthenticationScheme = "ApplicationCookie";
options.Cookies.ApplicationCookie.AuthenticationScheme = "ApplicationCookie";
options.Cookies.ApplicationCookie.CookieName = "oAuthInterop";
options.Cookies.ApplicationCookie.AutomaticChallenge = true;
options.Cookies.ApplicationCookie.AutomaticAuthenticate = true;
options.Cookies.ApplicationCookie.DataProtectionProvider = new DataProtectionProvider(new DirectoryInfo("d:\\development\\artefacts"),
configure =>
{
configure.SetApplicationName("TestAuthApp");
//configure.ProtectKeysWithCertificate("thumbprint");
});
options.Cookies.ApplicationCookie.ExpireTimeSpan = TimeSpan.FromMinutes(SessionLength);
}).AddDefaultTokenProviders();
// Add framework services.
services.AddMvc();
// Add cross site calls.
//TODO: implement with better security instead of allowing everything through.
services.AddCors(options => options.AddPolicy("AllowAll", p => p.AllowAnyOrigin()
.AllowAnyMethod()
.AllowAnyHeader().AllowCredentials()));
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
loggerFactory.AddConsole(Configuration.GetSection("Logging"));
loggerFactory.AddDebug();
app.UseIISPlatformHandler(options => options.AuthenticationDescriptions.Clear());
app.UseStaticFiles();
app.UseIdentity();
app.UseMvc();
}
}
A wild guess would be you are not setting withCredentials flag on your XMLHttpRequest when making cross-domain request from javascript via ajax. This flag basically controls whether to include credentials (such as cookies, authorization headers or client certificates) in cross-domain request. Why it still works in IE? Not completely sure, but maybe because proper implementation of this flag only appeared in IE10, and you might use another version of IE. If you use jquery to make requests, see here how to set this flag.
If that is not the case, please include your client-side code + request and response headers of Chrome's OPTIONS request.

Categories

Resources