Encode parameter before executing the request - c#

We are using .NET Core 3.1 and Swashbuckle.AspNetCore 5.6.3.
Some of our controller actions require User-Identity HTTP header to be sent in the request. The value of this User-Identity HTTP header is base64-encoded JSON object. Example:
JSON object
{
"email": "test#test.com"
}
is encoded in base64 as:
ewogICJFbWFpbCI6ICJ0ZXN0QHRlc3QuY29tIgp9.
We implemented the following filter operation which checks if controller action has [RequireUserIdentityFilterAttribute] attribute and adds a parameter accordingly.
public class RequireUserIdentityOperationFilter : IOperationFilter
{
public void Apply(OpenApiOperation operation, OperationFilterContext context)
{
// check if controller action has RequireUserIdentityFilterAttribute with reflection
var hasRequireUserIdentityFilterAttribute = context.MethodInfo
.GetCustomAttributes(true)
.OfType<RequireUserIdentityFilterAttribute>()
.Any();
if (hasRequireUserIdentityFilterAttribute)
{
operation.Parameters.Add(new OpenApiParameter
{
Description = "Base-64 encoded user email object. Example: { \"Email\": \"test#test.com\" } => ewogICJFbWFpbCI6ICJ0ZXN0QHRlc3QuY29tIgp9",
Name = "User-Identity",
In = ParameterLocation.Header,
Schema = new OpenApiSchema
{
Type = "string",
Example = new OpenApiString("ewogICJFbWFpbCI6ICJ0ZXN0QHRlc3QuY29tIgp9")
}
});
}
}
}
This is how it looks like in SwaggerUI:
It works fine as-is. User-Identity header is sent to the server.
However, it is not very user-friendly because user has to input a string which must be base64-encoded. Is it possible that we simplify the input so that the user will only have to input user email (ex. test#test.com) and C# will handle the base64 encoding before the request is sent to the server?

SwaggerUI uses JavaScript to send the requests to the server. You could inject a script that intercepts the request and changes the header before transmitting it to the server, e.g.
app.UseSwaggerUI(c =>
{
// ...
var adjustHeaders = #"(request) => {
let header = request.headers["User-Identity"];
if (header && header.length > 0) {
// header is a JSON object
if (header[0] == "{") header =
btoa(header);
// header is an email address
else if (header.indexOf("#") >= 0)
header = btoa(JSON.stringify({ email: header }));
// Otherwise assume that it is already encoded
request.headers["User-Identity"] = header;
}
return request;
}";
c.UseRequestInterceptor(adjustHeaders);
});
Above script is a pseudo-Javascript that can give you a starting point. Please test it in the browser whether it works in your situation and for the browsers your SwaggerUI targets.

Related

Web API MVC5 using Swagger not going to call API with "Try it out!" with Headers

I configured Swagger UI in my Web API MVC5. For testing API's, swagger works fine until I add some header parameters.
API's are created in such way that username & password fields are in headers and all other data is taken in the body.
Headers are added by IOperationFilter as suggested in Web Api How to add a Header parameter for all API in Swagger
and OperationFilter is added in SwaggerConfig.cs as: c.OperationFilter<AddHeaderParameters>();
public class AddHeaderParameters : IOperationFilter
{
public void Apply(Operation operation, SchemaRegistry schemaRegistry, ApiDescription apiDescription)
{
if (operation.parameters == null)
operation.parameters = new List<Parameter>();
operation.parameters.Add(new Parameter
{
name = "Account Username",
#in = "header",
type = "string",
required = true,
});
operation.parameters.Add(new Parameter
{
name = "Account Password",
#in = "header",
type = "string",
required = true,
});
}
}
Swagger UI with Headers
Required help that which thing I have missed or something done not correct.
I got the solution of issue facing as mentioned above in the question. Sharing here might be helpful for others.
As I created the class AddHeaderParameters for getting headers in swagger UI, I had given the header name self defined (that was actual cause of issue). The header name supposed to match as actual header name.
In my case, e.g. Header name was AccountUserName, which I used in swagger header with giving space as Account Username.
Correct way
operation.parameters.Add(new Parameter
{
name = "AccountUserName",
#in = "header",
type = "string",
required = true,
});

Botbuilder adaptive dialog storing user input

I'm using previews of the botbuilder adaptive dialog to gather some user info. I want to store this information in SQL. So my question is how can I gather the info from the "Property" in the textinput?
new TextInput
{
Prompt = new ActivityTemplate(question.Text),
Property = "user.userProfile" + question.Id
}
Use CodeAction or HttpRequest to call your api to store the information
Use this to make HTTP requests to any endpoint.
new HttpRequest()
{
// Set response from the http request to turn.httpResponse property in memory.
ResultProperty = "turn.httpResponse",
Method = HttpRequest.HttpMethod.POST,
Headers = new Dictionary<string,string> (), /* request header */
Body = JToken.FromObject(new
{
data = "#{user.userProfile" + question.Id + "}",
another = "#{user.another}"
}) /* request body */
});
Code Action
private async Task<DialogTurnResult> CodeActionSampleFn(DialogContext dc, System.Object options)
{
var userState = JObject.FromObject(dc.GetState().FirstOrDefault(x => x.Key == "user").Value);
//Get your data here
var data = userState.Value<JObject>("userProfile" + question.Id);
// call your API by HttpClient
//...
return dc.ContinueDialogAsync();
}
Check out more detail here
https://github.com/microsoft/BotBuilder-Samples/blob/master/experimental/adaptive-dialog/docs/recognizers-rules-steps-reference.md#HttpRequest
You can see examples of how to access state properties all over this page:
new SendActivity("Hello, #{user.name}")

ASP.NET Web API object parameter not being filled on post request

I am trying to set up a small ASP.NET Web API projects so I can post data to the database from a small React.JS project. I tried alot of sollutions but the results made no sense and I have no idea how to fix it anymore.
I have this very simple model:
public class Hour
{
public int WeekID { get; set; }
}
And this is my controller
[HttpPost]
public IHttpActionResult AddHour(Hour hour)
{
return Ok();
}
This is the method that I use to POST my data
export const SaveWeek = weekData=> {
const headers = new Headers();
headers.append("Content-Type", "application/json");
const Week= {
method: "POST",
headers,
mode: "cors",
body: weekData
};
console.log("Hours:");
// Returns {"WeekID": 1}
console.log(Hours.body);
return axios.post("http://localhost:52350/api/REST/AddHour", {
Week
});
};
The way I call this SaveWeek method in React is:
// The JSON parameter is for testing hard coded to: {"WeekID": 1}
handleSave = async json => {
const data = await SaveWeek(json);
console.log(data);
this.closeModal();
};
I know that the axios POST request works, the way I tested that is by changing the method to not use any parameters and looking at the result that where received:
[HttpPost]
public IHttpActionResult AddHour(Hour hour)
{
// This returns a string in which the data that I sent
// can be found.
string body = Request.Content.ReadAsStringAsync().Result;
return Ok();
}
The weird thing is that the body will be filled with data when the method does not contain any parameters, but when I provide the method with the Hour object parameter the body will be an empty string (""). And also the Hour object parameter wont be filled with the values that I provide it.
What am I doing wrong here?
According to https://github.com/axios/axios#axiosposturl-data-config axios.post has following signature
axios.post(url[, data[, config]])
So you just need to change your request to
export const SaveWeek = weekData => {
//headers should be simple object, not Headers
const headers = {
"Content-Type": "application/json"
};
//removed body, because we pass data as second parameter
//removed method, because 'axios.post' implies using "post" method
const Config = {
headers,
mode: "cors"
};
const url = "http://localhost:52350/api/REST/AddHour";
return axios.post(url, weekData, Config);
}
An incoming request to the ASP.Net Web API pipeline is read as a forward-only stream for super speed. Once it has been read it cannot be read again.
[HttpPost]
public IHttpActionResult AddHour(Hour hour)
{
// With model binding
// use hour.WeekID
}
In this first example model binding is already done and once it has been read it cannot be read again. Hence, Request.Content will be empty after that.
[HttpPost]
public IHttpActionResult AddHour()
{
// Without model binding
// use Request.Content
}
In second example it does not use model binding therefore still has the Request.Content property populated.
Use one or the other, not both, do not mix with MVC model binding which works differently.
A better explanation is available in this blog post
http://www.hackered.co.uk/articles/asp-net-web-api-why-is-the-request-Content-empty-when-the-model-is-populated

How to get the full url of the current api NOT of the original requestor

Background
I have a local backend API at this address:
http://localhost:54641/orders
And a clientside UI at:
http://localhost:3000/
What I need
The UI does a call to the backend, to list all available orders. This includes information on an attachment for each order. It may or not be there. If there is an attachment, you should get this response from the API, for each order:
{
"orderReference": "123456",
"actions": { "download": "http://localhost:54641/orders/123456/download" }
}
Actions will be {} if there's no attachment available.
However
What I do get, is this:
{
"orderReference": "123456",
"actions": { "download": "http://localhost:3000/orders/123456/download" }
}
Which doesn't exist, ofcourse.
What I have right now
Is this code to build the full url, which is going wrong:
var baseUrl = Request.RequestUri.GetLeftPart(UriPartial.Authority);
var uri = Url.Route("DownloadLabel", new {orderReference });
var fullUrl = $"{baseUrl}{uri}";
As in, it returns the requestor's full url path, not that of the current API.
Question
What can I do to get the API url in the response?
So, it should return like this:
http://localhost:54641/orders/123456/download
I guess you want this,
string fullUrl=HttpContext.Current.Request.Url.ToString();
var wantedUrl= fullUrl.SubString(0,fullUrl.IndexOf("/orders"))+"/orders/"+orderReference+"/download";
Found it, I used httpcontext instead:
var baseUrl = HttpContext.Current.Request.Url.GetLeftPart(UriPartial.Authority);
var uri = Url.Route("DownloadLabel", new {orderReference });
var fullUrl = $"{baseUrl}{uri}";

Read JSON post data in ASP.Net Core MVC

I've tried to find a solution for this, but all the ones coming up are for previous versions of ASP.Net.
I'm working with the JWT authentication middleware and have the following method:
private async Task GenerateToken(HttpContext context)
{
var username = context.Request.Form["username"];
var password = context.Request.Form["password"];
//Remainder of login code
}
This gets the sent data as if it was form data, but my Angular 2 front end is sending the data as JSON.
login(username: string, password: string): Observable<boolean> {
let headers = new Headers({ 'Content-Type': 'application/json' });
let options = new RequestOptions({ headers: headers });
let body = JSON.stringify({ username: username, password: password });
        return this.http.post(this._api.apiUrl + 'token', body, options)
            .map((response: Response) => {
                
            });
    }
My preferred solution is to send it as JSON, but I've been unsuccessful in retrieving the data. I know it's sending, because I can see it in fiddler, and if I use Postman and just send form data it works fine.
Basically I just need to figure out how to change this line to read the json data
var username = context.Request.Form["username"];
By the time it gets to your middleware the request stream has already been read, so what you can do here is Microsoft.AspNetCore.Http.Internal.EnableRewind on the Request and read it yourself
Site wide :
Startup.cs
using Microsoft.AspNetCore.Http.Internal;
Startup.Configure(...){
...
//Its important the rewind us added before UseMvc
app.Use(next => context => { context.Request.EnableRewind(); return next(context); });
app.UseMvc()
...
}
OR selective :
private async Task GenerateToken(HttpContext context)
{
context.Request.EnableRewind();
string jsonData = new StreamReader(context.Request.Body).ReadToEnd();
...
}

Categories

Resources