PostAsJsonAsync And Anonymous Types - 404 Not Found Errors - asp.net-mvc-4

I've been unsuccessfully trying to get this working.
I'm using AttributeRouting on the API and I have this method defined on my WebAPI:
[POST("update"), JsonExceptionFilter]
public HttpResponseMessage PostUpdate([FromJson] long id, DateTime oriDt, string notes, int score)
When I try to call this with the following code:
using (var httpClient = new HttpClient(CreateAuthorizingHandler(AuthorizationState)))
{
var args = new { id, oriDt, notes, score };
var postData = new List<KeyValuePair<string, string>>();
postData.Add(new KeyValuePair<string, string>("id", id.ToString()));
postData.Add(new KeyValuePair<string, string>("oriDt", oriDt.ToString(_dateService.DefaultDateFormatStringWithTime)));
postData.Add(new KeyValuePair<string, string>("notes ", notes));
postData.Add(new KeyValuePair<string, string>("score ", score.ToString(CultureInfo.InvariantCulture)));
var response = httpClient.PostAsJsonAsync(ApiRootUrl + "update", postData).Result;
if (response.IsSuccessStatusCode)
{
var data = response.Content.ReadAsAsync<bool>().Result;
return data;
}
return null;
}
The response is always 404 - not found. What am I missing here? I've tried using an anonymous object called args in the code with the same issue.
I've also tried it with and witout the [FromJson] attribute as well with the same results.

First, remove [FromJson]. With that, you have this action method.
public HttpResponseMessage PostUpdate(long id, DateTime oriDt,
string notes, int score)
If you POST to the URI below, it will work.
/update/123?oridt=somedate&notes=somenote&score=89
If you want to use request body to POST the fields (as you are doing with the client code), declare a class containing properties with name same as the field in request body.
public class MyDto
{
public long Id { get; set; }
public DateTime OriDt { get; set; }
public string Notes { get; set; }
public int Score { get; set; }
}
Then change the action method like this.
public HttpResponseMessage PostUpdate(MyDto dto)

Related

Display fields (from query) on Swagger UI from complex record

I have a complex record SearchProductsRequest in a GET request that receives the parameters by query (
/v1/products?ids=1,2,3&name=hombre&page=3&pageItems=4&sortField=name&sort=asc ).
app.MapGet(
$"/{ProductCatalogueApi.Version}/products",
(SearchProductsRequest request)
=> ProductApiDelegates.SearchProducts(
request));
In the record, I've implemented the bind async
public static ValueTask<SearchProductsRequest?> BindAsync(HttpContext httpContext, ParameterInfo parameter); and now the parameters from the URL automatically convert the parameters to SearchProductsRequest.
The request is working as intended, but we are using (Swashbuckle -> ) Swagger UI for development.
Swagger UI does not recognize the members from SearchProductsRequest to display them as input boxes. Is there a way to make swagger UI know them and display them so a user consulting the swagger endpoint can pass value through it?
I was hoping to get the following:
Until now, I've only managed to have the fields displayed in swagger if I have all of them in the Map.Get() explicitly.
EDIT:
Adding asked content
Record:
public record SearchProductsRequest
{
public IEnumerable<int>? Ids { get; private set; }
public string? Name { get; private set; }
public PaginationInfoRequest? PaginationInfo { get; private set; }
public SortingInfoRequest? SortingInfo { get; private set; }
public SearchProductsRequest(
IEnumerable<int>? ids,
string? name,
PaginationInfoRequest? PaginationInfo,
SortingInfoRequest? SortingInfo)
{
this.Ids = ids;
this.Name = name;
this.PaginationInfo = PaginationInfo;
this.SortingInfo = SortingInfo;
}
public static ValueTask<SearchProductsRequest?> BindAsync(
HttpContext httpContext,
ParameterInfo parameter)
{
var ids = ParseIds(httpContext);
var name = httpContext?.Request.Query["name"] ?? string.Empty;
PaginationInfoRequest? pagination = null;
SortingInfoRequest? sorting = null;
if (int.TryParse(httpContext?.Request.Query["page"], out var page)
&& int.TryParse(httpContext?.Request.Query["pageItems"], out var pageItems))
{
pagination = new PaginationInfoRequest(page, pageItems);
}
var sortField = httpContext?.Request.Query["sortField"].ToString();
if (!string.IsNullOrEmpty(sortField))
{
sorting = new SortingInfoRequest(
sortField,
httpContext?.Request.Query["sort"].ToString() == "asc");
}
return ValueTask.FromResult<SearchProductsRequest?>(
new SearchProductsRequest(
ids,
name!,
pagination,
sorting));
}
#pragma warning disable SA1011 // Closing square brackets should be spaced correctly
private static int[]? ParseIds(HttpContext httpContext)
{
int[]? ids = null;
var commaSeparatedIds = httpContext?.Request.Query["ids"]
.ToString();
if (!string.IsNullOrEmpty(commaSeparatedIds))
{
ids = commaSeparatedIds
.Split(",")
.Select(int.Parse)
.ToArray() ?? Array.Empty<int>();
}
return ids;
}
#pragma warning restore SA1011 // Closing square brackets should be spaced correctly
}
Delegate:
internal static async Task<IResult> SearchProducts(
ILogger<ProductApiDelegates> logger,
IMapper mapper,
SearchProductsRequest request,
IValidator<SearchProductsRequest> validator,
IProductService productService)
{
using var activity = s_activitySource.StartActivity("Search products");
var validationResult = await validator.ValidateAsync(request);
if (!validationResult.IsValid)
{
var errors = validationResult.GetErrors();
logger.LogError("Bad Request: {Errors}", errors);
return Results.BadRequest();
}
try
{
logger.LogInformation("Searching product details by name");
var filtersContainer = mapper.Map<SearchProductsFiltersContainer>(request);
var products = await productService.SearchProductsAsync(filtersContainer);
if (products == null)
{
return Results.NotFound();
}
var searchProducts = BuildSearchProducts(mapper, products);
var paginationInfo = await BuildPaginationInfo(filtersContainer, productService);
var response = new SearchProductsResponse(searchProducts, paginationInfo);
return Results.Ok(response);
}
catch (Exception ex)
{
logger.LogError(ex, "Error searching the products");
return Results.Problem();
}
}

How to send complex data to controller endpoint

I have this basic case:
[HttpPost("endpoint")]
public IActionResult Endpoint(DateTime date, string value, bool modifier)
{
return Ok($"{date}-{value}-{modifier}");
}
and I'm able to send a request to it with
var testContent = new FormUrlEncodedContent(new Dictionary<string, string>
{
{ "date", DateTime.Today.ToShortDateString() },
{ "value", "value1" },
{ "modifier", true.ToString() }
});
Instead I want my endpoint to be this instead
[HttpPost("endpointwithlist")]
public IActionResult EndpointWithList(DateTime date, List<string> value, bool modifier)
{
return Ok($"{date}-{value.FirstOrDefault()}-{modifier}");
}
How do I send this? I have tried the below, nothing works
var json = JsonConvert.SerializeObject(new { date, value = valueCollection.ToArray(), modifier });
var testContentWithList = new ByteArrayContent(Encoding.UTF8.GetBytes(json));
testContentWithList.Headers.ContentType = new MediaTypeHeaderValue("application/json");
You might create a model class for the payload
public class EndpointWithListModel
{
public DateTime Date {get; set;}
public List<string> Value {get; set;}
public bool Modifier {get; set;}
}
the method parameter then could use [FromBody] attribute
public IActionResult EndpointWithList([FromBody]EndpointWithListModel model)
then send the json to your POST method, example is here. Using HttpClient:
using (var client = new HttpClient())
{
var response = await client.PostAsync(
"http://yourUrl",
new StringContent(json, Encoding.UTF8, "application/json"));
}
if your variables(date, valueController and modifier) are in the right type, following code should work.
var json = JsonConvert.SerializeObject(new { date:date, value : valueCollection.ToArray(), modifier:modifier });

How to send object which contains IEnumerable via Refit on NetCore?

I have to send a request object via Refit which contains 2 IEnumerable and one string, but for some reason I can't send the object forward.
I've tried to use all the paramets from the interface. Ex: [Query(CollectionFormat.Csv)] or Multi / Pipes but no success.
I've also tried to create my own CustomUrlParameterFormatter but unfortunately here I'm stuck, because I don't see a good way to retrieve the name of the property from the object request that I'm sending.
The code for CustomUrlParameterFormatter
public class CustomUrlParameterFormatter : IUrlParameterFormatter
{
public string Format(object value, ParameterInfo parameterInfo)
{
if(value is IEnumerable enumerable)
{
var result = ToQueryString(enumerable, parameterInfo.Name);
return result;
}
return string.Empty;
}
public static string ToQueryString(IEnumerable query, string parameterName)
{
var values = query.Cast<object>().Select(ToString).ToArray();
var separator = parameterName + "=";
return values.Any() ? separator + string.Join("&" + separator, values) : "";
}
public static string ToString(object value)
{
var json = JsonConvert.SerializeObject(value).Replace("\\\"", "\"").Trim('"');
return Uri.EscapeUriString(json);
}
}
The Call from the IService that I'm using
[Get("/TestMethod")]
Task<HttpResponseMessage> TestMethod([Query]TestRequestDTO requestDTO, [Header("X-Correlation-ID")] string correlationId);
The Request object
public class TestRequestDTO
{
public IEnumerable<long> EnumOne { get; set; }
public IEnumerable<long> EnumTwo { get; set; }
public string MethodString { get; set; }
}
Also the RefitClient configuration
var refitSettings = new RefitSettings();
refitSettings.UrlParameterFormatter = new CustomUrlParameterFormatter();
services.AddRefitClient<IService>(refitSettings)
.ConfigureHttpClient(c => c.BaseAddress = new Uri(settings.Services.IService));
What I'm trying to achieve is something like
TestMethod?EnumOne =123&EnumOne =321&EnumTwo=123&EnumTwo=321&methodString=asdsaa
and instead I'm receiving other behavior
without CustomUrlParameterFormatter()
TestMethod?EnumOne=System.Collections.Generic.List`1%5BSystem.Int64%5D&EnumTwo=System.Collections.Generic.List`1%5BSystem.Int64%5D&MethodString=sdf

ASP.NET Core Paypal Implementation

I try to implement a PayPal cart payment in ASP.NET Core. I have a working example in ASP.NET MVC 5 and I try to convert it to ASP.NET Core but I had no success. The point that I can not resolve is how to get the values that I have to get the transactionID, amount paid and Order ID. In ASP.NET MVC 5 the IPN action is as follows:
public ActionResult IPN()
{
System.Net.ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12;
var formVals = new Dictionary<string, string>();
formVals.Add("cmd", "_notify-validate");
string response = GetPayPalResponse(formVals, true);
if (response == "VERIFIED")
{
string transactionID = Request["txn_id"];
string sAmountPaid = Request["mc_gross"];
string orderID = Request["custom"];
:
:
In my ASP.NET Core application the IPN action is executed by PayPal and I have a VERIFIED response but I can not get the next three values. I have tried various ways to get these values without success.
My initial approach was the following:
string transactionID = Request.Query["txn_id"];
string sAmountPaid = Request.Query["mc_gross"];
string orderID = Request.Query["custom"];
Can someone suggest me a way to get these values?
I found a solution to my problem and I will post it just in case someone wants to do something similar.
[Route("PayPal/IPN")]
[HttpPost]
public ActionResult IPN()
{
System.Net.ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12;
PayPalRespond response = GetPayPalResponse();
if (response.RespondType == RespondTypeEnum.Verified)
{
System.IO.File.AppendAllText(_env.WebRootPath + Path.DirectorySeparatorChar.ToString() + "data.txt", $"{DateTime.Now.ToString()} {response.JsonData}." + Environment.NewLine);
Order order = GetOrder(154);
//check the amount paid
if (order.Total <= response.AmountPaid)
{
// IPN Order successfully transacted. Save changes to database
return Ok();
}
else
{
// Amount Paid is incorrect
}
}
else
{
// Not verified
}
return Content("");
}
PayPalRespond GetPayPalResponse()
{
PayPalRespond output = new PayPalRespond();
var formVals = new Dictionary<string, string>();
formVals.Add("cmd", "_notify-validate");
string paypalUrl = UseSandbox ? "https://www.sandbox.paypal.com/cgi-bin/webscr" : "https://www.paypal.com/cgi-bin/webscr";
HttpWebRequest req = (HttpWebRequest)WebRequest.Create(paypalUrl);
// Set values for the request back
req.Method = "POST";
req.ContentType = "application/x-www-form-urlencoded";
byte[] param;
using (var ms = new MemoryStream(2048))
{
Request.Body.CopyTo(ms);
param = ms.ToArray();
}
string strRequest = Encoding.ASCII.GetString(param);
var QueryValues = System.Web.HttpUtility.ParseQueryString(strRequest);
output.Data = new List<QueryValue>();
foreach (var item in QueryValues.AllKeys)
{
if (item.Equals("txn_id"))
output.TransactionID = QueryValues[item];
else if (item.Equals("mc_gross"))
{
CultureInfo culture = CultureInfo.CreateSpecificCulture("en-US");
NumberStyles style = NumberStyles.Number;
Decimal amountPaid = 0;
Decimal.TryParse(QueryValues[item], style, culture, out amountPaid);
output.AmountPaid = amountPaid;
}
else if (item.Equals("custom"))
output.OrderID = QueryValues[item];
output.Data.Add(new QueryValue { Name = item, Value = QueryValues[item] });
}
output.JsonData = Newtonsoft.Json.JsonConvert.SerializeObject(output.Data);
StringBuilder sb = new StringBuilder();
sb.Append(strRequest);
foreach (string key in formVals.Keys)
{
sb.AppendFormat("&{0}={1}", key, formVals[key]);
}
strRequest += sb.ToString();
req.ContentLength = strRequest.Length;
//Send the request to PayPal and get the response
string response = "";
using (StreamWriter streamOut = new StreamWriter(req.GetRequestStream(), System.Text.Encoding.ASCII))
{
streamOut.Write(strRequest);
streamOut.Close();
using (StreamReader streamIn = new StreamReader(req.GetResponse().GetResponseStream()))
{
response = streamIn.ReadToEnd();
}
}
output.RespondType = response.Equals("VERIFIED") ? RespondTypeEnum.Verified : RespondTypeEnum.Invalid;
return output;
}
The enumerator and the classes that you will need are the following:
public enum RespondTypeEnum { Verified, Invalid }
public class PayPalRespond
{
public RespondTypeEnum RespondType { get; set; }
public List<QueryValue> Data { get; set; }
public string JsonData { get; set; }
public string TransactionID { get; set; }
public string OrderID { get; set; }
public Decimal AmountPaid { get; set; }
}
public class QueryValue
{
public string Name { get; set; }
public string Value { get; set; }
}

MVC Web Api returning serialized response instead of css

I am having an issue returning css from a web api controller. The code takes a request for a css file and returns it after reading it from the database.
The problem is that the web api code seems to be serializing the response and returning that instead of the css itself.
Here you can see a link tag that the browser is sending to the server which should return css. You can also see that the response looks like a serialization of my css instead of just the css string.
My request and response headers:
My controller looks like this:
public HttpResponseMessage Get(string fileName, string siteId, int id)
{
var fileData = ReadSomeCssFromTheDatabase();
var result = new HttpResponseMessage(HttpStatusCode.OK);
result.Content = new ByteArrayContent(fileData);
result.Content.Headers.ContentType = new MediaTypeHeaderValue("text/css");
result.Headers.CacheControl = new CacheControlHeaderValue();
result.Headers.CacheControl.MaxAge = TimeSpan.FromHours(0);
result.Headers.CacheControl.MustRevalidate = true;
return result;
}
There is a “text/css” formatter installed that is being created but not being hit for some reason.
public class CssFormatter : MediaTypeFormatter
{
public CssFormatter()
{
SupportedMediaTypes.Add(new MediaTypeHeaderValue("text/css"));
}
public override Task<object> ReadFromStreamAsync(Type type, Stream readStream, HttpContent content, IFormatterLogger formatterLogger)
{
var taskCompletionSource = new TaskCompletionSource<object>();
try
{
var memoryStream = new MemoryStream();
readStream.CopyTo(memoryStream);
var s = System.Text.Encoding.UTF8.GetString(memoryStream.ToArray());
taskCompletionSource.SetResult(s);
}
catch (Exception e)
{
taskCompletionSource.SetException(e);
}
return taskCompletionSource.Task;
}
public override bool CanReadType(Type type)
{
return type == typeof(string);
}
public override bool CanWriteType(Type type)
{
return false;
}
}
What am I doing wrong?
Your formatter would not be hit because you are not going through content negotiation process (as you are returning HttpResponseMessage in your action...you could use Request.CreateResponse<> to make conneg process run)
You are trying to 'write' the css content right?...but i see that CanWriteType is returning 'false' and also you seem to be overriding ReadFromStreamAsync instead of WriteToStreamAsync?
An example of how you could do(from what i understood about the above scenario):
public class DownloadFileInfo
{
public string FileName { get; set; }
public string SiteId { get; set; }
public int Id { get; set; }
}
public HttpResponseMessage Get([FromUri]DownloadFileInfo info)
{
// validate the input
//Request.CreateResponse<> would run content negotiation and get the appropriate formatter
//if you are asking for text/css in Accept header OR if your uri ends with .css extension, you should see your css formatter getting picked up.
HttpResponseMessage response = Request.CreateResponse<DownloadFileInfo>(HttpStatusCode.OK, info);
response.Headers.CacheControl = new CacheControlHeaderValue();
response.Headers.CacheControl.MaxAge = TimeSpan.FromHours(0);
response.Headers.CacheControl.MustRevalidate = true;
return response;
}
public class CssFormatter : MediaTypeFormatter
{
public CssFormatter()
{
SupportedMediaTypes.Add(new MediaTypeHeaderValue("text/css"));
}
public override bool CanReadType(Type type)
{
return false;
}
public override bool CanWriteType(Type type)
{
return type == typeof(DownloadFileInfo);
}
public override Task WriteToStreamAsync(Type type, object value, Stream writeStream, HttpContent content, TransportContext transportContext)
{
//use the 'value' having DownloadFileInfo object to get the details from the database.
// Fead from database and if you can get it as a Stream, then you just need to copy it to the 'writeStream'
}
}