.net core 2.2 webapi and handling multypart request - asp.net-core

I have .net core 2.2 webapi as backend.
My backend have to handling the requests (photo and text) from mobile application as multypart-form request.
Plz hint me how it to prpvide on backend?

this problem has solved
public class SwaggerFileOperationFilter : IOperationFilter
{
public void Apply(Operation operation, OperationFilterContext context)
{
if (operation.OperationId == "MailWithPhotos")
{
operation.Parameters = new List<IParameter>
{
new NonBodyParameter
{
Name = "awr_file",
Required = false,
Type = "file",
In = "formData"
},
new NonBodyParameter
{
Name = "awr_message",
Required = true,
Type = "string",
In = "formData"
},
new NonBodyParameter
{
Type = "string",
In = "header",
Name = "Authorization",
Description = "token",
Required = true
}
};
}
}
}
[HttpPost]
[ProducesResponseType(typeof(ResponseMail), 200)]
public async Task<PipeResponse> MailWithPhotos([FromForm] MailwithPhoto fIleUploadAPI)
{
var file = fIleUploadAPI.awr_file; // OK!!!
var message = fIleUploadAPI.awr_message; // OK!!!
var tokenA = Request.Headers["Authorization"]; // OK!!!
var fileContentStream11 = new MemoryStream();
await fIleUploadAPI.awr_file.CopyToAsync(fileContentStream11);
await System.IO.File.WriteAllBytesAsync(Path.Combine(folderPath,
fIleUploadAPI.awr_file.FileName),
fileContentStream11.ToArray());
}
public class MailwithPhoto
{
public string awr_message { get; set; }
public string Authorization { get; set; }
public IFormFile awr_file { get; set; }
}

Related

FluentValidation failure not returning BadRequest

I have wired up FluentValidation as per instructions, and when debuging test I can see that model is invalid based on the test setup, but exception is not thrown, but rather method on the controller is being executed. This is on 3.1 with EndPoint routing enabled. Is there anything else one needs to do to get this to work and throw. What happens is that validation obviously runs; it shows as ModelState invalid and correct InstallmentId is invalid, but it keeps processing in Controller instead of throwing exception.
services.AddMvc(
options =>
{
options.EnableEndpointRouting = true;
//// options.Filters.Add<ExceptionFilter>();
//// options.Filters.Add<CustomerRequestFilter>();
})
.AddFluentValidation(
config =>
{
config.RegisterValidatorsFromAssemblyContaining<Startup>();
})
Command and Validator
public class ProcessManualPayment
{
public class Command
: CustomerRequest<Result?>
{
public Guid PaymentPlanId { get; set; }
public Guid InstallmentId { get; set; }
public Guid PaymentCardId { get; set; }
}
public class Validator : AbstractValidator<Command>
{
public Validator()
{
this.RuleFor(x => x.CustomerId)
.IsValidGuid();
this.RuleFor(x => x.PaymentPlanId)
.IsValidGuid();
this.RuleFor(x => x.InstallmentId)
.IsValidGuid();
this.RuleFor(x => x.PaymentCardId)
.IsValidGuid();
}
}
Controller
[Authorize]
[HttpPost]
[Route("payments")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
public async Task<IActionResult> ProcessManualPayment(
[FromBody]
ProcessManualPayment.Command command)
{
Test
[Fact]
public async Task When_Command_Has_Invalid_Payload_Should_Fail()
{
var client = this.factory.CreateClient();
// Arrange
var validCmd = new ProcessManualPayment.Command()
{
CustomerId = Guid.NewGuid(),
PaymentPlanId = Guid.NewGuid(),
InstallmentId = Guid.NewGuid(),
PaymentCardId = Guid.NewGuid(),
};
var validCmdJson = JsonConvert.SerializeObject(validCmd, Formatting.None);
var jObject = JObject.Parse(validCmdJson);
jObject["installmentId"] = "asdf";
var payload = jObject.ToString(Formatting.None);
// Act
var content = new StringContent(payload, Encoding.UTF8, MediaTypeNames.Application.Json);
var response = await client.PostAsync(MakePaymentUrl, content);
var returned = await response.Content.ReadAsStringAsync();
response.StatusCode.ShouldBe(HttpStatusCode.BadRequest);
}
[Fact]
public async Task When_Payload_Is_Null_Should_Fail()
{
// Arrange
var client = this.factory.CreateClient();
// Act
var response = await client.PostAsJsonAsync(MakePaymentUrl, null);
// Assert
response.StatusCode.ShouldBe(HttpStatusCode.BadRequest);
}
GuidValidator
public class GuidValidator : PropertyValidator
{
public GuidValidator()
: base("'{PropertyName}' value {AttemptedValue} is not a valid Guid.")
{
}
protected override bool IsValid(PropertyValidatorContext context)
{
context.MessageFormatter.AppendArgument("AttemptedValue", context.PropertyValue ?? "'null'");
if (context.PropertyValue == null)
{
return false;
}
Guid.TryParse(context.PropertyValue.ToString(), out var value);
return IsValid(value);
}
private static bool IsValid(Guid? value) =>
value.HasValue
&& !value.Equals(Guid.Empty);
}
Mystery solved, I was missing [ApiController] attribute on the controller.

Asp.Net Core 3.1 Model Binding Doesn't Work

Model binding doesn't work on asp.net core 3.1 version. I have no problem with previous dotnet core versions or .net framework version. Somebody help me please.
Best regards
JavaScript code:
var postData = { "createdFrom": "2020-07-18", "createdTo": "2020-07-19", "message": "test", "logLevel": "Error" };
fetch('/log/list', {
method: "post",
body: JSON.stringify(postData),
headers: {
"Content-Type": "application/json"
}
})
.then(res => res.json())
.then(res => {
console.log(res);
}).catch(err => {
console.error(err);
});
C# code:
[HttpPost]
public IActionResult List([FromBody] LogSearchModel searchModel) // searchModel is null
{
string rawBodyStr = "";
using (StreamReader reader = new StreamReader(Request.Body, Encoding.UTF8))
{
rawBodyStr = reader.ReadToEndAsync().Result;
}
// rawBodyStr is null
}
public class LogSearchModel
{
[DisplayName("Başlangıç tarihi")]
[UIHint("DateNullable")]
public DateTime? CreatedFrom { get; set; }
[DisplayName("Bitiş tarihi")]
[UIHint("DateNullable")]
public DateTime? CreatedTo { get; set; }
[DisplayName("Açıklama")]
public string Message { get; set; }
[DisplayName("Olay türü")]
public int LogLevel { get; set; }
}
Startup.cs
public void ConfigureServices(IServiceCollection services)
{
services.AddScoped<LogManager>();
services.AddControllersWithViews();
}
Your LogLevel is type of int,but you pass the string data.
Change from:
var postData = { "createdFrom": "2020-07-18", "createdTo": "2020-07-19",
"message": "test", "logLevel": "Error" };
To:
var postData = { "createdFrom": "2020-07-18", "createdTo": "2020-07-19",
"message": "test", "logLevel": 1 };

Swashbuckle.AspNetCore 5.0.0-rc4 UploadFileFilter doesn't work

I need to add upload file for Swashbuckle.AspNetCore 5.0.0-rc4. In earlier version it works like:
public class SwaggerUploadFileParametersFilter : IOperationFilter
{
public void Apply(Operation operation, SchemaRegistry schemaRegistry, ApiDescription apiDescription)
{
if (operation.parameters != null)
{
var attribute =
apiDescription.ActionDescriptor.GetCustomAttributes<UploadFileParametersAttribute>()
.FirstOrDefault();
if (attribute != null)
{
operation.consumes.Add("multipart/form-data");
operation.parameters.Add(new Parameter
{
name = "file",
required = true,
type = "file",
#in = "formData"
}
);
}
}
}
}
[UploadFileParameters]
public async Task<IHttpActionResult> MyMethod([FromUri]MyMethodParams parameters)
I try to implement it using Microsoft.OpenApi.Models objects:
public class SwaggerUploadFileParametersFilter : IOperationFilter
{
public void Apply(OpenApiOperation operation, OperationFilterContext context)
{
var actionAttributes = context.MethodInfo.GetCustomAttributes<UploadFileParametersAttribute>().FirstOrDefault();
if (actionAttributes != null)
{
operation.RequestBody = new OpenApiRequestBody()
{
Content =
{
["multipart/form-data"] = new OpenApiMediaType()
{
Schema = new OpenApiSchema()
{
Properties =
{
["file"] = new OpenApiSchema()
{
Description = "Select file",
Type = "file"
}
}
}
}
}
};
}
}
}
But it doesn't work. I don't see file component in swagger
I took your code and some documentation from Swagger File Upload
I modified your code and added small fix
public class SwaggerUploadFileParametersFilter : IOperationFilter
{
public void Apply(OpenApiOperation operation, OperationFilterContext context)
{
var parameters = operation.Parameters;
if (parameters == null || parameters.Count == 0)
{
return;
}
var isUploadFile = context.ApiDescription.ActionDescriptor.Parameters.Any(x => x.ParameterType == typeof(IFormFile));
if (isUploadFile)
{
operation.RequestBody = new OpenApiRequestBody()
{
Content =
{
["multipart/form-data"] = new OpenApiMediaType()
{
Schema = new OpenApiSchema()
{
Type = "object",
Properties =
{
["file"] = new OpenApiSchema()
{
Description = "Select file", Type = "string", Format = "binary"
}
}
}
}
}
};
}
}
}
And controller:
[HttpPost]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesDefaultResponseType]
public async Task<IActionResult> UploadFileAsync([FromForm] IFormFile file)

Serialization empty

I try to serialize a custom object, but it ends up being empty.
The result I want to have is :
{
"accessRules": [
{
"method": "GET",
"path": "/*"
}
],
"redirection":"https://www.mywebsite.com/"
}'
Here are my classes:
[Serializable]
public class AccessRule
{
[SerializeAs(Name = "method")]
string Method { get; set; }
[SerializeAs(Name = "path")]
string Path { get; set; }
public AccessRule(string method, string path)
{
this.Method = method;
this.Path = path;
}
}
[Serializable]
public class RequestBody
{
[SerializeAs(Name = "accessRules")]
AccessRule[] AccessRules
{
get { return arList.ToArray(); }
}
private List<AccessRule> arList;
[SerializeAs(Name = "redirection")]
string Redirection { get; set; }
public RequestBody(string method, string path, string redirection)
{
this.arList = new List<AccessRule>();
this.arList.Add(new AccessRule(method, path));
this.Redirection = redirection;
}
}
var RequestBody = new RequestBody("GET", "/*", "https://www.mywebsite.com");
And it leads to an empty json string.
If i do :
var RequestBody = new { accessRules = new[] { new { method = "GET", path = "/*" } }, redirection = "https://www.mywebsite.com" };
It works.
I use RestSharp 104.4.0.0, also tried with Newtonsoft.Json v6.0.0.0
What am I missing here ?
Thanks.

Getting email from oauth authentication (Microsoft)

How can I get the email from microsoft account? I'm doing the following:
public ActionResult ExternalLoginCallback(string returnUrl)
{
AuthenticationResult result = OAuthWebSecurity.VerifyAuthentication(Url.Action("ExternalLoginCallback", new { ReturnUrl = returnUrl }));
//...
string email = null;
if (result.Provider.ToLower() == "google")
{
email = result.ExtraData["email"];
}
else if (result.Provider.ToLower() == "facebook")
{
email = result.ExtraData["username"];
}
else if (result.Provider.ToLower() == "microsoft")
{
email = result.ExtraData["????"];
}
}
For google and facebook I'm able to get the email but I can't with microsoft? What kew should I use?
Solution:
public class MicrosoftScopedClient : IAuthenticationClient
{
private string clientId;
private string clientSecret;
private string scope;
private const string baseUrl = "https://login.live.com/oauth20_authorize.srf";
private const string tokenUrl = "https://login.live.com/oauth20_token.srf";
public MicrosoftScopedClient(string clientId, string clientSecret, string scope)
{
this.clientId = clientId;
this.clientSecret = clientSecret;
this.scope = scope;
}
public string ProviderName
{
get { return "Microsoft"; }
}
public void RequestAuthentication(HttpContextBase context, Uri returnUrl)
{
string url = baseUrl + "?client_id=" + clientId + "&redirect_uri=" + HttpUtility.UrlEncode(returnUrl.ToString()) + "&scope=" + HttpUtility.UrlEncode(scope) + "&response_type=code";
context.Response.Redirect(url);
}
public AuthenticationResult VerifyAuthentication(HttpContextBase context)
{
string code = context.Request.QueryString["code"];
string rawUrl = context.Request.Url.ToString();
//From this we need to remove code portion
rawUrl = Regex.Replace(rawUrl, "&code=[^&]*", "");
IDictionary<string, string> userData = GetUserData(code, rawUrl);
if (userData == null)
return new AuthenticationResult(false, ProviderName, null, null, null);
string id = userData["id"];
string username = userData["email"];
userData.Remove("id");
userData.Remove("email");
AuthenticationResult result = new AuthenticationResult(true, ProviderName, id, username, userData);
return result;
}
private IDictionary<string, string> GetUserData(string accessCode, string redirectURI)
{
string token = QueryAccessToken(redirectURI, accessCode);
if (token == null || token == "")
{
return null;
}
var userData = GetUserData(token);
return userData;
}
private IDictionary<string, string> GetUserData(string accessToken)
{
ExtendedMicrosoftClientUserData graph;
var request =
WebRequest.Create(
"https://apis.live.net/v5.0/me?access_token=" + EscapeUriDataStringRfc3986(accessToken));
using (var response = request.GetResponse())
{
using (var responseStream = response.GetResponseStream())
{
using (StreamReader sr = new StreamReader(responseStream))
{
string data = sr.ReadToEnd();
graph = JsonConvert.DeserializeObject<ExtendedMicrosoftClientUserData>(data);
}
}
}
var userData = new Dictionary<string, string>();
userData.Add("id", graph.Id);
userData.Add("username", graph.Name);
userData.Add("name", graph.Name);
userData.Add("link", graph.Link == null ? null : graph.Link.AbsoluteUri);
userData.Add("gender", graph.Gender);
userData.Add("firstname", graph.FirstName);
userData.Add("lastname", graph.LastName);
userData.Add("email", graph.Emails.Preferred);
return userData;
}
private string QueryAccessToken(string returnUrl, string authorizationCode)
{
var entity =
CreateQueryString(
new Dictionary<string, string> {
{ "client_id", this.clientId },
{ "redirect_uri", returnUrl },
{ "client_secret", this.clientSecret},
{ "code", authorizationCode },
{ "grant_type", "authorization_code" },
});
WebRequest tokenRequest = WebRequest.Create(tokenUrl);
tokenRequest.ContentType = "application/x-www-form-urlencoded";
tokenRequest.ContentLength = entity.Length;
tokenRequest.Method = "POST";
using (Stream requestStream = tokenRequest.GetRequestStream())
{
var writer = new StreamWriter(requestStream);
writer.Write(entity);
writer.Flush();
}
HttpWebResponse tokenResponse = (HttpWebResponse)tokenRequest.GetResponse();
if (tokenResponse.StatusCode == HttpStatusCode.OK)
{
using (Stream responseStream = tokenResponse.GetResponseStream())
{
using (StreamReader sr = new StreamReader(responseStream))
{
string data = sr.ReadToEnd();
var tokenData = JsonConvert.DeserializeObject<OAuth2AccessTokenData>(data);
if (tokenData != null)
{
return tokenData.AccessToken;
}
}
}
}
return null;
}
private static readonly string[] UriRfc3986CharsToEscape = new[] { "!", "*", "'", "(", ")" };
private static string EscapeUriDataStringRfc3986(string value)
{
StringBuilder escaped = new StringBuilder(Uri.EscapeDataString(value));
// Upgrade the escaping to RFC 3986, if necessary.
for (int i = 0; i < UriRfc3986CharsToEscape.Length; i++)
{
escaped.Replace(UriRfc3986CharsToEscape[i], Uri.HexEscape(UriRfc3986CharsToEscape[i][0]));
}
// Return the fully-RFC3986-escaped string.
return escaped.ToString();
}
private static string CreateQueryString(IEnumerable<KeyValuePair<string, string>> args)
{
if (!args.Any())
{
return string.Empty;
}
StringBuilder sb = new StringBuilder(args.Count() * 10);
foreach (var p in args)
{
sb.Append(EscapeUriDataStringRfc3986(p.Key));
sb.Append('=');
sb.Append(EscapeUriDataStringRfc3986(p.Value));
sb.Append('&');
}
sb.Length--; // remove trailing &
return sb.ToString();
}
protected class ExtendedMicrosoftClientUserData
{
public string FirstName { get; set; }
public string Gender { get; set; }
public string Id { get; set; }
public string LastName { get; set; }
public Uri Link { get; set; }
public string Name { get; set; }
public Emails Emails { get; set; }
}
protected class Emails
{
public string Preferred { get; set; }
public string Account { get; set; }
public string Personal { get; set; }
public string Business { get; set; }
}
}
AuthConfig.cs
public static class AuthConfig
{
public static void RegisterAuth()
{
Dictionary<string, object> MicrosoftsocialData = new Dictionary<string, object>();
MicrosoftsocialData.Add("Icon", "../Content/icons/microsoft.png");
OAuthWebSecurity.RegisterClient(new MicrosoftScopedClient("XXXXXXXX", "YYYYYYYYYYYYY",
"wl.basic wl.emails"), "Microsoft", MicrosoftsocialData);
//......
}
}
Usage:
public ActionResult ExternalLoginCallback(string returnUrl)
{
AuthenticationResult result = OAuthWebSecurity.VerifyAuthentication(Url.Action("ExternalLoginCallback", new { ReturnUrl = returnUrl }));
//...
string email = null;
if (result.Provider.ToLower() == "google")
{
email = result.ExtraData["email"];
}
else if (result.Provider.ToLower() == "facebook")
{
email = result.ExtraData["username"];
}
else if (result.Provider.ToLower() == "microsoft")
{
email = result.UserName;
}
}
Based on: How OAuthWebSecurity to obtain emails for different oauth clients, but Microsoft Client doesn’t return email, it didn’t include scope “wl.emails”
or even simpler: https://stackoverflow.com/a/22723713/1586498
var mo =
new Microsoft.Owin.Security.MicrosoftAccount.MicrosoftAccountAuthenticationOptions
{
CallbackPath = new Microsoft.Owin.PathString("/Callbacks/External"),//register at oAuth provider
ClientId = "<<yourclientid>>",
ClientSecret = "<<yourclientsecret>>",
Provider = new Microsoft.Owin.Security.MicrosoftAccount.MicrosoftAccountAuthenticationProvider
{
OnAuthenticated = (context) =>
{
context.Identity.AddClaim(new Claim(providerKey, context.Identity.AuthenticationType));
context.Identity.AddClaim(new Claim(ClaimTypes.Name, context.Identity.FindFirstValue(ClaimTypes.Name)));
return System.Threading.Tasks.Task.FromResult(0);
}
}
};
mo.Scope.Add("wl.basic");
mo.Scope.Add("wl.emails"); //HERE IS THE GOLD
app.UseMicrosoftAccountAuthentication(mo);
and my way of grabbing them:
var externalIdentity = await AuthenticationManager.GetExternalIdentityAsync(DefaultAuthenticationTypes.ExternalCookie);
externalIdentity.Claims.FirstOrDefault(c => c.Type.Equals(ClaimTypes.Email));
amp's answer really helped me out.
Also want to mention that you have to check the 'Live SDK support' checkbox when you register your application (https://apps.dev.microsoft.com/) - otherwise the OAuth service complains that you don't have a client secret (even if you do).
Just wanted to add how to do this without using the AuthConfig.cs stuff in case anyone is interested (a bit more manual, but it makes it easier to understand if you're not familiar with the framework):
public ActionResult LoginWithMicrosoftAccount(CancellationToken cancellationToken)
{
var client = new MicrosoftScopedClient(appID, appsecret, "wl.basic wl.emails");
var urlNoQueryString = Request.Url.GetLeftPart(UriPartial.Path);
AuthenticationResult result = null;
if(Request.QueryString["error"]!= null)
{//Microsoft service returns error
return View();
}
if (Request.QueryString["code"] != null)
{
result = client.VerifyAuthentication(this.HttpContext);
//at this point, you should get the username from result.UserName
}
if(Request.QueryString["code"]==null || result.UserName == null)
{//will do the redirection
client.RequestAuthentication(this.HttpContext, new Uri(urlNoQueryString));
}
return View();
}