I tried adding this in the bootstrapper in the ApplicationStartup override.
pipelines.AfterRequest.AddItemToStartOfPipeline(ctx =>
{
ctx.Request.Headers["x-fcr-version"] = "1";
});
Its giving me errors.
Can anyone point me in the right direction?
Notice how you are trying to set the Request while trying to manipulate the Response ?
Try this..
protected override void RequestStartup(TinyIoCContainer container, IPipelines pipelines, NancyContext context)
{
base.RequestStartup(container, pipelines, context);
pipelines.AfterRequest.AddItemToEndOfPipeline(c =>
{
c.Response.Headers["x-fcr-version"] = "1";
});
}
This is what my Response looks like..
Or .. you can use Connection Negotiation if you're going to set it at the module level...
Get["/"] = parameters => {
return Negotiate
.WithModel(new RatPack {FirstName = "Nancy "})
.WithMediaRangeModel("text/html", new RatPack {FirstName = "Nancy fancy pants"})
.WithView("negotiatedview")
.WithHeader("X-Custom", "SomeValue");
};
Since this question is about adding headers to a nancy request, which I need to do as I need to add an origin header, and some others when making requests to my app api.
In order to get it to work, I did the following:
//create headers dictionary
var myHeaders = new Dictionary<string, IEnumerable<string>>();
myHeaders.Add("origin",new List<String>{"https://my.app.com"});
//..... snip - adding other headers ....//
var uri = new Uri("https://my.api.com");
var request = new Nancy.Request("OPTIONS", uri, null, myHeaders,"127.0.0.1", null);
I found reading the nancy request source source useful, as the null parameters, (body and protocolVersion) and I passed through get initialized if not set.
For some reason the answer with content negotiation is not working in my case but I found another way:
Get["result"] = x=>
{
...
var response = Response.AsText(myModel, "application/json");
response.Headers.Add("Access-Control-Allow-Origin", "http://example.com");
response.Headers.Add("Access-Control-Allow-Credentials", "true");
return response;
};
Related
I have strange problem when trying to use PostAsJsonAsync.
In body I get this json with strange leading and trailing characters:
99 {
"SessionReferenceId":"f39dc178-279e-4e3a-bda9-a16829eb0e45",
"GameReferenceId":"netent_es_starburst",
"CurrencyCode":"EUR",
"LossLimit":100,
"ClientType":1
}
0
and on API side this can't be binded and I get Error message that the request cannot have empty body.
Code on the sending side is like this:
using(var client = new SGSClient()) {
var model = new CashSessionCreateModel()
{
ClientType = ClientType.DesktopBrowser,
CurrencyCode = "EUR",
GameReferenceId = "netent_es_starburst",
LossLimit = 100,
SessionReferenceId = "f39dc178-279e-4e3a-bda9-a16829eb0e45"
};
HttpResponseMessage response = client.PostAsJsonAsync(apiUrl, model).Result;
}
Adding HTTPClient configuration:
public SGSHttpClient()
{
var appSettingsFilePath = $"Configuration\\appSettings.json";
// Build Configuration
var configuration = new ConfigurationBuilder()
.SetBasePath(Directory.GetCurrentDirectory())
.AddJsonFile(appSettingsFilePath, false, true)
.AddEnvironmentVariables()
.Build();
var sgsConfig = configuration.GetSection("SGSClient");
//Base url for SGS service
var _clientConfig = sgsConfig.GetSection("Client").GetChildren();
var baseAddress = _clientConfig.FirstOrDefault(o => o.Key.Equals("BaseAddress"));
BaseAddress = new Uri(baseAddress.Value);
//Adding headers
DefaultRequestHeaders.Accept.Clear();
DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
_dateUTC = DateTime.UtcNow.ToString("u");
DefaultRequestHeaders.Add("DateUtc", _dateUTC);
}
This and one other SO question were the only useful pages I found when I was scratching my head over this same issue today. Neither question had satisfactory explanations though. So after some more head scratching, I was able to set my mind at ease. Hopefully my answer on the other question explains the behavior to your satisfaction!
tl;dr
PostAsJsonAsync uses JsonContent which uses Transfer-Encoding: chunked instead of Content-Length and the "strange characters" are chunk headers (and perfectly valid HTTP/1.1)
You can use this type format to send may be helpfull
var postTask = client.PostAsJsonAsync<ClassNameOfObject>(baseUri + "ControllerName/ActionName?UserID=" + UserID, object);
postTask.Wait();
I am setting up a PHP API and a web-page based on client-side Blazor. But for some reason CORS is triggered and my login process or any requests to my PHP pages result in CORS errors.
I started out testing my PHP API with a C# console app and the Blazor app, I tried using without any database access to test the functionality. The Blazor is right now running with Preview 9. The PHP version is 5.3.8. I could in theory update it, but several other active projects are running on it and I do not have any test environment. MySQL version 5.5.24.
First I figured it might have been because I was running it on my local machine, so I pushed it to the website where the PHP and MySQL is also running. Still I run into this CORS error.
I am still just testing this, so I have tried setting it to allow any origin. I have not had any experience with CORS before this. Pretty sure I ought to be able to add PHP code in each file I access that should allow CORS, but since it should all be on the same website, I figure CORS should not even be relevant?
PHP Code:
function cors() {
// Allow from any origin
if (isset($_SERVER['HTTP_ORIGIN'])) {
// Decide if the origin in $_SERVER['HTTP_ORIGIN'] is one
// you want to allow, and if so:
header("Access-Control-Allow-Origin: {$_SERVER['HTTP_ORIGIN']}");
header('Access-Control-Allow-Credentials: true');
header('Access-Control-Max-Age: 86400'); // cache for 1 day
}
// Access-Control headers are received during OPTIONS requests
if ($_SERVER['REQUEST_METHOD'] == 'OPTIONS') {
if (isset($_SERVER['HTTP_ACCESS_CONTROL_REQUEST_METHOD']))
// may also be using PUT, PATCH, HEAD etc
header("Access-Control-Allow-Methods: GET, POST, OPTIONS");
if (isset($_SERVER['HTTP_ACCESS_CONTROL_REQUEST_HEADERS']))
header("Access-Control-Allow-Headers: {$_SERVER['HTTP_ACCESS_CONTROL_REQUEST_HEADERS']}");
exit(0);
}
echo "You have CORS!";
}
cors();
C# code using the injected HttpClient:
var resp = await Http.GetStringAsync(link);
The error I get is:
Access to fetch at 'https://titsam.dk/ntbusit/busitapi/requestLoginToken.php' from origin 'https://www.titsam.dk' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource. If an opaque response serves your needs, set the request's mode to 'no-cors' to fetch the resource with CORS disabled.
The response I hoped to get was that the link I use return a token for the login as it does for my API.
Is it because its running client side maybe and this triggers CORS? But that does not seem to explain why I cannot make it allow all.
Update:
My C# code in OnInitializedAsync:
link = API_RequestLoginTokenEndPoint;
Http.DefaultRequestHeaders.Add("User-Agent", "HttpClientFactory-Sample");
Http.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Authorization", "basic:testuser:testpass");
var requestMessage = new HttpRequestMessage(HttpMethod.Get, link);
requestMessage.Properties[WebAssemblyHttpMessageHandler.FetchArgs] = new
{
credentials = "include"
};
var response = await Http.SendAsync(requestMessage);
var responseStatusCode = response.StatusCode;
var responseBody = await response.Content.ReadAsStringAsync();
output = responseBody + " " + responseStatusCode;
Update 2:
It finally works. The C# code I linked is the solution Agua From Mars suggested and it solved the problem to use SendAsync with a HttpRequestMessage and adding the Fetch property include credentials to it. Another alternative was to add this line to the startup:
WebAssemblyHttpMessageHandler.DefaultCredentials = FetchCredentialsOption.Include;
Then I could keep doing what I did to begin with, using GetStringAsync as it becomes the default.
await Http.GetStringAsync(API_RequestLoginTokenEndPoint);
So all the solutions Agua From Mars suggested worked. But I encountered a browser problem, where it kept the CORS issue in the cache somehow even after it had gotten solved, so it seemed like nothing had changed. Some code changes would show a different result, but I guess the CORS part was kept alive. With Chrome it helped opening a new pane or window. In my Opera browser this was not enough, I had to close all panes with the site open to ensure it would clear the cache and then opening a new window or pane with the site works in Opera as well. I had already in both browsers trying to use ctrl-F5 and Shift-F5 to get them to clear the cache. This did not change anything.
I hope this will help others avoid spending 2-3 days on an issue like this.
update 3.1-preview3
In 3.1-preview3, we cannot use the fetch option per message, the options is global
WebAssemblyHttpMessageHandlerOptions.DefaultCredentials = FetchCredentialsOption.Include;
WebAssemblyHttpMessageHandler has been removed. The HttpMessageHanlder used is WebAssembly.Net.Http.HttpClient.WasmHttpMessageHandler from WebAssembly.Net.Http but don't include WebAssembly.Net.Http in your depencies or the application will failled to launch.
If you want to use the HttpClientFactory you can implement like that :
public class CustomDelegationHandler : DelegatingHandler
{
private readonly IUserStore _userStore;
private readonly HttpMessageHandler _innerHanler;
private readonly MethodInfo _method;
public CustomDelegationHandler(IUserStore userStore, HttpMessageHandler innerHanler)
{
_userStore = userStore ?? throw new ArgumentNullException(nameof(userStore));
_innerHanler = innerHanler ?? throw new ArgumentNullException(nameof(innerHanler));
var type = innerHanler.GetType();
_method = type.GetMethod("SendAsync", BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.InvokeMethod) ?? throw new InvalidOperationException("Cannot get SendAsync method");
WebAssemblyHttpMessageHandlerOptions.DefaultCredentials = FetchCredentialsOption.Include;
}
protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
request.Headers.Authorization = new AuthenticationHeaderValue(_userStore.AuthenticationScheme, _userStore.AccessToken);
return _method.Invoke(_innerHanler, new object[] { request, cancellationToken }) as Task<HttpResponseMessage>;
}
}
public void ConfigureServices(IServiceCollection services)
{
services.AddTransient(p =>
{
var wasmHttpMessageHandlerType = Assembly.Load("WebAssembly.Net.Http")
.GetType("WebAssembly.Net.Http.HttpClient.WasmHttpMessageHandler");
var constructor = wasmHttpMessageHandlerType.GetConstructor(Array.Empty<Type>());
return constructor.Invoke(Array.Empty<object>()) as HttpMessageHandler;
})
.AddTransient<CustomDelegationHandler>()
.AddHttpClient("MyApiHttpClientName")
.AddHttpMessageHandler<CustonDelegationHandler>();
}
3.0 -> 3.1-preview2
On Blazor client side your need to tell to the Fetch API to send credentials (cookies and authorization header).
It's describe in the Blazor doc Cross-origin resource sharing (CORS)
requestMessage.Properties[WebAssemblyHttpMessageHandler.FetchArgs] = new
{
credentials = FetchCredentialsOption.Include
};
ex:
#using System.Net.Http
#using System.Net.Http.Headers
#inject HttpClient Http
#code {
private async Task PostRequest()
{
Http.DefaultRequestHeaders.Authorization =
new AuthenticationHeaderValue("Bearer", "{OAUTH TOKEN}");
var requestMessage = new HttpRequestMessage()
{
Method = new HttpMethod("POST"),
RequestUri = new Uri("https://localhost:10000/api/TodoItems"),
Content =
new StringContent(
#"{""name"":""A New Todo Item"",""isComplete"":false}")
};
requestMessage.Content.Headers.ContentType =
new System.Net.Http.Headers.MediaTypeHeaderValue(
"application/json");
requestMessage.Content.Headers.TryAddWithoutValidation(
"x-custom-header", "value");
requestMessage.Properties[WebAssemblyHttpMessageHandler.FetchArgs] = new
{
credentials = FetchCredentialsOption.Include
};
var response = await Http.SendAsync(requestMessage);
var responseStatusCode = response.StatusCode;
var responseBody = await response.Content.ReadAsStringAsync();
}
}
You can set up this option globaly with WebAssemblyHttpMessageHandlerOptions.DefaultCredentials static proprerty.
Or you can implement a DelegatingHandler and set it up in DI with the HttpClientFactory:
public class CustomWebAssemblyHttpMessageHandler : WebAssemblyHttpMessageHandler
{
internal new Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
return base.SendAsync(request, cancellationToken);
}
}
public class CustomDelegationHandler : DelegatingHandler
{
private readonly CustomWebAssemblyHttpMessageHandler _innerHandler;
public CustomDelegationHandler(CustomWebAssemblyHttpMessageHandler innerHandler)
{
_innerHandler = innerHandler ?? throw new ArgumentNullException(nameof(innerHandler));
}
protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
request.Properties[WebAssemblyHttpMessageHandler.FetchArgs] = new
{
credentials = "include"
};
return _innerHandler.SendAsync(request, cancellationToken);
}
}
In Setup.ConfigureServices
services.AddTransient<CustomWebAssemblyHttpMessageHandler>()
.AddTransient<WebAssemblyHttpMessageHandler>()
.AddTransient<CustomDelegationHandler>()
.AddHttpClient(httpClientName)
.AddHttpMessageHandler<CustomDelegationHandler>();
Then you can create an HttpClient for your API with IHttpClientFactory.CreateClient(httpClientName)
To use the IHttpClientFactory you need to install Microsoft.Extensions.Http package.
3.0-preview3 => 3.0-preview9
Replace WebAssemblyHttpMessageHandler with BlazorHttpMessageHandler
I wanted to make my WebAPI application change the used SessionStateBehavior based on action attributes like that:
[HttpPost]
[Route("api/test")]
[SetSessionStateBehavior(SessionStateBehavior.Required)] // <--- This modifies the behavior
public async Task<int> Test(){}
It seems, however, that the only place I can change the session behavior is inside my HttpApplication's Application_PostAuthorizeRequest (or in similar places, early in the request lifetime), otherwise I get this error:
'HttpContext.SetSessionStateBehavior' can only be invoked before 'HttpApplication.AcquireRequestState' event is raised.
So, at that point no controller or action resolution is done, so I don't know what action will be called in order to check its attributes.
So, I am thinking of resolving the action manually.
I started with these lines of code to resolve the controller first:
var httpCtx = HttpContext.Current;
var ctrlSel = GlobalConfiguration.Configuration.DependencyResolver.GetService(typeof(IHttpControllerSelector)) as IHttpControllerSelector;
var actionSel = GlobalConfiguration.Configuration.DependencyResolver.GetService(typeof(IHttpActionSelector)) as IHttpActionSelector;
HttpControllerDescriptor controllerDescriptor = ctrlSel.SelectController(httpCtx.Request);
But in the last line I can't get the proper HttpRequestMessage from the request.
Any idea ho how get that?
This is not inside a controller, so I don't have it ready there.
Or, is there a better way to do this?
I am trying to see the disassembled code of the framework to copy portions of it, but I am quite lost at this point...
UPDATE:
This is the closest I got to resolving the action manually, but it doesn't work:
I have registered those two services:
container.RegisterType<IHttpControllerSelector, DefaultHttpControllerSelector>();
container.RegisterType<IHttpActionSelector, ApiControllerActionSelector>();
...and try to get the required session behavior like that:
private SessionStateBehavior GetDesiredSessionBehavior(HttpContext httpCtx)
{
var config = GlobalConfiguration.Configuration;
var diResolver = config.Services;
var ctrlSel = diResolver.GetService(typeof(IHttpControllerSelector)) as IHttpControllerSelector;
var actionSel = diResolver.GetService(typeof(IHttpActionSelector)) as IHttpActionSelector;
if (ctrlSel is null || actionSel is null)
{
return DefaultSessionBehavior;
}
var method = new HttpMethod(httpCtx.Request.HttpMethod);
var requestMsg = new HttpRequestMessage(method, httpCtx.Request.Url);
requestMsg.Properties.Add(HttpPropertyKeys.RequestContextKey, httpCtx.Request.RequestContext);
requestMsg.Properties.Add(HttpPropertyKeys.HttpConfigurationKey, config);
httpCtx.Request.Headers.Cast<string>().ForEach(x => requestMsg.Headers.Add(x, httpCtx.Request.Headers[x]));
var httpRouteData = httpCtx.Request.RequestContext.RouteData;
var routeData = config.Routes.GetRouteData(requestMsg);
requestMsg.Properties.Add(HttpPropertyKeys.HttpRouteDataKey, routeData);
requestMsg.SetRequestContext(new HttpRequestContext(){RouteData = routeData });
requestMsg.SetConfiguration(config);
var route = config.Routes["DefaultApi"];
requestMsg.SetRouteData(routeData ?? route.GetRouteData(config.VirtualPathRoot, requestMsg));
var routeHandler = httpRouteData.RouteHandler ?? new WebApiConfig.SessionStateRouteHandler();
var httpHandler = routeHandler.GetHttpHandler(httpCtx.Request.RequestContext);
if (httpHandler is IHttpAsyncHandler httpAsyncHandler)
{
httpAsyncHandler.BeginProcessRequest(httpCtx, ar => httpAsyncHandler.EndProcessRequest(ar), null);
}
else
{
httpHandler.ProcessRequest(httpCtx);
}
var values = requestMsg.GetRouteData().Values; // Hm this is empty and makes the next call fail...
HttpControllerDescriptor controllerDescriptor = ctrlSel.SelectController(requestMsg);
IHttpController controller = controllerDescriptor?.CreateController(requestMsg);
if (controller == null)
{
return DefaultSessionBehavior;
}
var ctrlContext = CreateControllerContext(requestMsg, controllerDescriptor, controller);
var actionCtx = actionSel.SelectAction(ctrlContext);
var attr = actionCtx.GetCustomAttributes<ActionSessionStateAttribute>().FirstOrDefault();
return attr?.Behavior ?? DefaultSessionBehavior;
}
I have an alternative hack to make it work (send header values from the client to modify the session behavior), but it would be nice if the version above worked.
UPDATE:
Eventually, I went with setting the session behavior based on a client header value and validating the validity of sending that header based on the action attributes later-on in the request lifetime. If someone can solve the action resolution code I was fighting with above, feel free to post the answer here.
I don't know if this is going to be helpful for you, but I was just following a Pluralsight course (https://app.pluralsight.com/player?course=implementing-restful-aspdotnet-web-api) and in the Versioning chapter the author shows how to implement a controller selector where he does have access to the request.
The controller selector looks like:
public class CountingKsControllerSelector : DefaultHttpControllerSelector
{
private HttpConfiguration _config;
public CountingKsControllerSelector(HttpConfiguration config)
: base(config)
{
_config = config;
}
public override HttpControllerDescriptor SelectController(HttpRequestMessage request)
{
var controllers = GetControllerMapping();
var routeData = request.GetRouteData();
var controllerName = (string)routeData.Values["controller"];
HttpControllerDescriptor descriptor;
if (controllers.TryGetValue(controllerName, out descriptor))
{
[...]
return descriptor;
}
return null;
}
}
And it's registered in WebApiConfig with:
config.Services.Replace(typeof(IHttpControllerSelector),
new CountingKsControllerSelector(config));
I am sending an httpPost parameter "client" of type IdentityServer4.Models.Client via a C# console application to a C# web api and client is always null.
If I send a different object using the same code, the parameter is received just fine. This seems to be a problem specific to Identity Server 4 and I don't know what's going on.
Server code:
[HttpPost]
public JsonResult Post(IdentityServer4.Models.Client client){
return Json(new { result = true });
}
Client code:
private static async Task Register(string clientName){
var controllerName = "BasicClient";
var basicClientApi = string.Format("http://localhost:5100/api/{0}", controllerName);
using (var httpClient = new HttpClient()){
var clientData = new IdentityServer4.Models.Client();
var client = new { client = clientData };
client.client.ClientName = clientName;
var json = JsonConvert.SerializeObject(client);
var content = new StringContent(json, Encoding.UTF8, "application/json");
content.Headers.ContentType = new MediaTypeHeaderValue("application/json");
var response = await httpClient.PostAsync(basicClientApi, content);
if (!response.IsSuccessStatusCode)
{
Console.WriteLine(response.StatusCode);
}
else
{
var rawResponse = await response.Content.ReadAsStringAsync();
JObject o = JObject.Parse(rawResponse);
Console.WriteLine(o.ToString());
}
}
}
EDIT
After applying [FromBody] and unwrapping the object, I am still getting null for client in the receiving Web API. One thing caught my eye on the console application debug screen though.. see the image below:
The actual client variable is null in the console application, yet JsonConvert was able to serialize it into something. What's that about?
You are wrapping your model inside an anonymous object, which will turn its JSON representation into something which has nothing in common with your original Client class.
This:
var clientData = new IdentityServer4.Models.Client();
var client = new { client = clientData };
client.client.ClientName = clientName;
var json = JsonConvert.SerializeObject(client);
Will result in a JSON similar to the following:
{
"client": {
"clientName": "foo",
"anotherProperty": "bar",
// other properties omitted for brevity
}
}
But what you really want is just the Client object:
{
"clientName": "foo",
"anotherProperty": "bar",
// other properties omitted for brevity
}
Do not wrap your clientData, just serialize it directly to follow the model inside your MVC Action Method:
var clientData = new IdentityServer4.Models.Client();
clientData.ClientName = clientName;
var json = JsonConvert.SerializeObject(clientData);
For everything to be working, you have to tell the model binder explicitly where to expect the data.
Use [FromBody] attribute on the model.
[FromBody]: Use the configured formatters to bind data from the request body. The formatter is selected based on content type of the request.
[HttpPost]
public IActionResult Post([FromBody]IdentityServer4.Models.Client client) {
return Json(new { result = true });
}
Reference Model Binding in ASP.NET Core
You're not going to believe this, but ultimately the reason why IdentityServer.Models.Client was null in the Web Api post parameter was because the class is decorated with [DebuggerDisplay("{ClientId}")] and I did not provide a ClientId in my test application, so it was always showing up as null when in fact it actually WAS THERE THE WHOLE TIME. I am glad this issue is behind me, but I am very angry about this "feature".
I found a blog post that shows how to "shim" familiar things like HttpResponseMessage back into ASP.NET Core MVC, but I want to know what's the new native way to do the same thing as the following code in a REST Post method in a Controller:
// POST audit/values
[HttpPost]
public System.Net.Http.HttpResponseMessage Post([FromBody]string value)
{
var NewEntity = _repository.InsertFromString(value);
var msg = new System.Net.Http.HttpResponseMessage(System.Net.HttpStatusCode.Created);
msg.Headers.Location = new Uri(Request.RequestUri + NewEntity.ID.ToString());
return msg;
}
In an ASP.NET Core MVC project, I can't seem to get Request.RequestUri.
I tried inspecting Request, and I was able to make a function like this:
private string UriStr(HttpRequest Request)
{
return Request.Scheme + "://" + Request.Host + Request.Path; // Request.Path has leading /
}
So I could write UriStr(Request) instead. But I'm not sure that's right. I feel like I'm hacking my way around, and not using this correctly.
A related question for earlier non-Core ASP.NET MVC versions asks how to get the base url of the site.
Personally, I use :
new Uri(request.GetDisplayUrl())
GetDisplayUrl fully un-escaped form (except for the QueryString)
GetEncodedUrl - fully escaped form suitable for use in HTTP headers
These are extension method from the following namespace : Microsoft.AspNetCore.Http.Extensions
A cleaner way would be to use a UriBuilder:
private static Uri GetUri(HttpRequest request)
{
var builder = new UriBuilder();
builder.Scheme = request.Scheme;
builder.Host = request.Host.Value;
builder.Path = request.Path;
builder.Query = request.QueryString.ToUriComponent();
return builder.Uri;
}
(not tested, the code might require a few adjustments)
Here's a working code. This is based off #Thomas Levesque answer which didn't work well when the request is from a custom port.
public static class HttpRequestExtensions
{
public static Uri ToUri(this HttpRequest request)
{
var hostComponents = request.Host.ToUriComponent().Split(':');
var builder = new UriBuilder
{
Scheme = request.Scheme,
Host = hostComponents[0],
Path = request.Path,
Query = request.QueryString.ToUriComponent()
};
if (hostComponents.Length == 2)
{
builder.Port = Convert.ToInt32(hostComponents[1]);
}
return builder.Uri;
}
}