Receiving empty response on a PostAsJsonAsync - asp.net-core

I have this API action:
[HttpPost("MergeProjects")]
public async Task<ActionResult<(bool Result, string Message)>> MergeProjects(IEnumerable<DesignProjectViewModel> projects)
{
var mergeResult = await _projectService.MergeProjects(projects);
return Ok(mergeResult);
}
Here is the code in action returning a message with Ok:
On client side, I have this code:
public async Task<(bool Result, string Message)> MergeProjects(IEnumerable<DesignProjectViewModel> selectedProjects)
{
var response = await ConnectingClient.PostAsJsonAsync("api/designProject/mergeProjects", selectedProjects);
if (!response.IsSuccessStatusCode) return (false, "Failed");
var result = await response.Content.ReadAsStringAsync();
var result1 = await response.Content.ReadFromJsonAsync<(bool, string)>();
return JsonConvert.DeserializeObject<(bool Result, string Message)>(result);
}
I'm experimenting.
I'm not able to find anything in the response.Contents to parse:
What could be the issue. The API returns a json response.

You can try to put the data into an object,and return an object:
Api:
[HttpPost("MergeProjects")]
public async TestObject MergeProjects(IEnumerable<DesignProjectViewModel> projects)
{
var mergeResult = await _projectService.MergeProjects(projects);
return new TestObject { MyProperty1= mergeResult .Item1, MyProperty2=mergeResult .Item2};
}
Model:
public class TestObject
{
public bool MyProperty1 { get; set; }
public string MyProperty2 { get; set; }
}
Controller:
public async Task<TestObject> MergeProjects(IEnumerable<DesignProjectViewModel> selectedProjects)
{
var response = await ConnectingClient.PostAsJsonAsync("api/designProject/mergeProjects", selectedProjects);
if (!response.IsSuccessStatusCode) return new TestObject { MyProperty1=false,MyProperty2="Failed"};
var result = await response.Content.ReadAsStringAsync();
return JsonConvert.DeserializeObject<TestObject>(result);
}

Related

Is there a clean way to accept an array of values in a query parameter?

I'm trying to cleanly find a way to retrieve a list of IDs in a query parameter. i.e.
/users?ids=abc123,def4141,ggg555
/users?ids=abce123&def4141&ggg555
I want both routes to be mapped to a single endpoint in .NET Core.
If I use a string, /users?ids=abce123&ids=def4141&ids=ggg555 doesn't work properly since we'll only get the first ID, abce123
public ActionResult GetByIds(string ids) {
// do stuff
}
If I use a list of strings, it'll work for the second route /users?ids=abce123&def4141&ggg555 but not the first.
public ActionResult GetByIds(List<string> ids) {
// do stuff
}
You need a model binder
public class ArrayModelBinder : IModelBinder
{
public Task BindModelAsync(ModelBindingContext bindingContext)
{
if (!bindingContext.ModelMetadata.IsEnumerableType)
{
bindingContext.Result = ModelBindingResult.Failed();
return Task.CompletedTask;
}
var providedValue = bindingContext.ValueProvider
.GetValue(bindingContext.ModelName)
.ToString();
if (string.IsNullOrEmpty(providedValue))
{
bindingContext.Result = ModelBindingResult.Success(null);
return Task.CompletedTask;
}
var genericType = bindingContext.ModelType.GetTypeInfo().GenericTypeArguments[0];
var converter = TypeDescriptor.GetConverter(genericType);
var objectArray = providedValue.Split(new[] { "," }, StringSplitOptions.RemoveEmptyEntries)
.Select(x => converter.ConvertFromString(x.Trim()))
.ToArray();
var idsArray = Array.CreateInstance(genericType, objectArray.Length);
objectArray.CopyTo(idsArray, 0);
bindingContext.Model = idsArray;
bindingContext.Result = ModelBindingResult.Success(bindingContext.Model);
return Task.CompletedTask;
}
}
And in your controller:
[HttpGet]
public async Task<IActionResult> GetUsersByIds([FromQuery][ModelBinder(BinderType = typeof(ArrayModelBinder))] IEnumerable<string> ids)
{
// for example something like this to get the users from db
var users = await _service.Users.GetByIdsAsync(ids, trackChanges: false);
return Ok(users);
}
and now this will work: /users?ids=abc123,def4141,ggg555
** Credits to codemaze for this

Asp.net Core 3.1 Web Api return custom error message from IActionResult

Is it possible to return custom error messages to client from Asp.Net Core 3.1 Web Api? I've tried a few different things to set the "ReasonPhrase" with no luck. I have tried using StatusCode:
return StatusCode(406, "Employee already exists");
I tried to return using HttpResponseMessage:
HttpResponseMessage msg = new HttpResponseMessage();
msg.StatusCode = HttpStatusCode.NotAcceptable;
msg.ReasonPhrase = "Employee alredy exists";
return (IActionResult)msg;
I am trying to return a message to the client calling the method that the employee already exists:
public async Task<IActionResult> CreateEmployee([FromBody] EmployeeImport Employee)
{
var exists = await employeeService.CheckForExistingEmployee(Employee);
if (exists > 0)
{
//return StatusCode(406, "Employee already exists");
HttpResponseMessage msg = new HttpResponseMessage();
msg.StatusCode = HttpStatusCode.NotAcceptable;
msg.ReasonPhrase = "Employee already exists";
return (IActionResult)msg;
}
}
This is the code in the client:
public async Task<ActionResult>AddEmployee(EmployeeImport employee)
{
var message = await CommonClient.AddEmployee(employee);
return Json(message.ReasonPhrase, JsonRequestBehavior.AllowGet);
}
public async Task<HttpResponseMessage> AddEmployee(EmployeeImport employee)
{
var param = Newtonsoft.Json.JsonConvert.SerializeObject(employee);
HttpContent contentPost = new StringContent(param, System.Text.Encoding.UTF8, "application/json");
var response = await PerformPostAsync("entity/NewEmployee", contentPost);
return response;
}
protected async Task<HttpResponseMessage> PerformPostAsync(string requestUri, HttpContent c)
{
_webApiClient = new HttpClient { BaseAddress = _baseAddress };
_webApiClient.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", accessToken);
var webApiResponse = await _webApiClient.PostAsync(requestUri, c);
return webApiResponse;
}
To do this, you can create a Custom Error class that implements the IActionResult interface as follows:
public class CustomError : IActionResult
{
private readonly HttpStatusCode _status;
private readonly string _errorMessage;
public CustomError(HttpStatusCode status, string errorMessage)
{
_status = status;
_errorMessage = errorMessage;
}
public async Task ExecuteResultAsync(ActionContext context)
{
var objectResult = new ObjectResult(new
{
errorMessage = _errorMessage
})
{
StatusCode = (int)_status,
};
context.HttpContext.Features.Get<IHttpResponseFeature>().ReasonPhrase = _errorMessage;
await objectResult.ExecuteResultAsync(context);
}
}
And use the following form :
[HttpGet]
public IActionResult GetEmployee()
{
return new CustomError(HttpStatusCode.NotFound, "The employee was not found");
}
Try changing
return (IActionResult)msg;
to
return Task.FromResult(BadRequest(msg) as IActionResult);

How do you manage the visible input fields accepted in an API HttpPost request?

In my API I have a Create method in my controller that accepts all of the models fields, but in the method I'm excluding the ID field since on a create it's generated. But in Swagger it's showing the following.
Is there a way for it not to show the following part?
"id": 0
Is a viewmodel how I should go about this?
I tried the following, but can't get it to work.
public class PartVM
{
public string Name { get; set; }
}
public interface IPartService
{
Task<Part> CreatePart(PartVM part);
Task<IEnumerable<Part>> GetParts();
Task<Part> GetPart(int partId);
}
public class PartService : IPartService
{
private readonly AppDbContext _appDbContext;
public PartService(AppDbContext appDbContext)
{
_appDbContext = appDbContext;
}
public async Task<Part> CreatePart(PartVM part)
{
var _part = new Part()
{
Name = part.Name
};
var result = await _appDbContext.Parts.AddAsync(_part);
await _appDbContext.SaveChangesAsync();
return result.Entity;
}
}
Here's my controller.
[Route("api/[controller]")]
[ApiController]
public class PartsController : ControllerBase
{
private readonly IPartService _partService;
public PartsController(IPartService partService)
{
_partService = partService;
}
[HttpPost]
public async Task<ActionResult<Part>> CreatePart(PartVM part)
{
try
{
if (part == null)
return BadRequest();
var _part = new Part()
{
Name = part.Name
};
var createdPart = await _partService.CreatePart(_part);
return CreatedAtAction(nameof(GetPart),
new { id = createdPart.Id}, createdPart);
}
catch (Exception /*ex*/)
{
return StatusCode(StatusCodes.Status500InternalServerError, "Error creating new record in the database");
}
}
I'm getting a build error saying "CS1503 Argument 1: cannot convert from 'MusicManager.Shared.Part' to 'MusicManager.Server.Data.ViewModels.PartVM'".
It's refering to "_part" in this line "var createdPart = await _partService.CreatePart(_part);".
Any help is appreciated, thank you!
you have a CreatePart method which receives a PartVM model, but you are sending a Part Model to it
change your method to this :
public async Task<Part> CreatePart(Part part)
{
var result = await _appDbContext.Parts.AddAsync(_part);
await _appDbContext.SaveChangesAsync();
return result.Entity;
}

SSE Eventsource Asp.net Core 3.1

[ApiController]
[Route("/SSE/[action]")]
public class SSEController : Controller
{
private static ConcurrentBag<StreamWriter> clients;
static SSEController()
{
clients = new ConcurrentBag<StreamWriter>();
}
[HttpPost]
public async Task SSECallbackMsg()
{
await CallbackMsg("test");
}
private async Task CallbackMsg(string test)
{
foreach (var client in clients)
{
try
{
var data = string.Format(test);
await client.WriteAsync(data);
await client.FlushAsync();
client.Dispose();
}
catch (Exception)
{
StreamWriter ignore;
clients.TryTake(out ignore);
}
}
}
[HttpGet]
public HttpResponseMessage GETSubscibe()
{
HttpResponseMessage response = new HttpResponseMessage();
response.Content = new PushStreamContent((a, b, c) =>
{ OnStreamAvailable(a, b, c); }, "text/event-stream");
return response;
}
private void OnStreamAvailable(Stream stream, HttpContent content,
TransportContext context)
{
var client = new StreamWriter(stream,Encoding.UTF8);
clients.Add(client);
}
}
Javascript Method of calling above is like
function listenForServerEvents() {
var source = new EventSource('https://localhost:5002/SSE/GETSubscibe');
source.addEventListener("open", function (event) {
console.log('onopen');
}, false);
source.addEventListener("error", function (event) {
if (event.eventPhase == EventSource.CLOSED) {
source.close();
}
}, false);
source.addEventListener("message", function (event) {
console.log('onmessage: ' + event.data);
}, false);
}
when executing, above js function, i am getting error as EventSource's response has a MIME type ("application/json") that is not "text/event-stream". Aborting the connection.
Should add anything in startup.cs or is there any mistake., If anyone knows ,kindly help

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.