Reputation: 3457
This particular case should not be a duplicate of any other thread - I believe I've checked them all and I don't believe any refer to this case specifically.
I have this Controller:
namespace Cantrel.Application.CantrelSearchApi.Controllers
{
[Route("api/[controller]")]
[Authorize]
public class MonitorsController : Controller
{
private readonly IMonitoringApiService monitoringService;
private readonly IClientsApiService clientsService;
private readonly ILogger<MonitorsController> logger;
public MonitorsController(IMonitoringApiService monitoringService,
IClientsApiService clientsService,
ILogger<MonitorsController> logger)
{
this.monitoringService = monitoringService;
this.clientsService = clientsService;
this.logger = logger;
}
[HttpGet("{id}")]
public async Task<IActionResult> Get(string id)
{
if (string.IsNullOrEmpty(id))
{
return BadRequest("No id was provided. Please provide a valid monitor id or subject id.");
}
try
{
MonitorDto monitor;
if (Guid.TryParse(id, out Guid monitorId))
{
monitor = await GetByMonitorId(monitorId);
}
else
{
monitor = await GetBySubjectId(id);
}
if (monitor == null)
{
return NotFound();
}
return Json(monitor);
}
catch (Exception ex)
{
logger.LogError($"[{Request.Path.Value}]: {ex.ToString()}");
return new StatusCodeResult(500);
}
}
[HttpPost]
public async Task<IActionResult> Post([FromBody] MonitorsPostRequest request)
{
//logger.LogError("We are here"); <--
if (request == null)
{
return BadRequest("Request could not be parsed.");
}
if (string.IsNullOrEmpty(request.SubjectId))
{
return BadRequest("SubjectId is required for a monitor.");
}
if (string.IsNullOrEmpty(request.Subject))
{
return BadRequest("Subject is required for a monitor.");
}
if (request.BillingCodeId == Guid.Empty)
{
return BadRequest("A billing code is required for a monitor.");
}
try
{
var clientId = GetClientId();
var billingCodeId = new BillingCodeId(request.BillingCodeId);
//check permissions <--
var permissions = await clientsService.AuthorizeSearchLicenseAsync(
new SearchAuthorizationRequest()
{
ClientId = clientId,
BillingCodeId = billingCodeId,
Categories = request.Categories?.Split(new[] { ',', ' ' }, StringSplitOptions.RemoveEmptyEntries)
});
if (!permissions.Success)
{
if (!permissions.BillingCodeValid)
{
return BadRequest($"The billing code provided is not active for the account.");
}
if (permissions.Licensing == null)
{
return BadRequest($"The User does not have access to requested functionality, please contact sales if you would like to include additional permission or if your permissions are incorrect.");
}
else
{
return BadRequest($"The User does not have access to {string.Join(", ", permissions.Licensing.Select(l => l.Categories))} Categories, please contact sales if you would like to include additional permission or if your permissions are incorrect.");
}
}
var createRequest = new CreateMonitorRequest()
{
ClientId = clientId,
BillingCodeId = billingCodeId,
Subject = request.Subject,
SubjectId = request.SubjectId,
Categories = request.Categories?.Split(new[] { ',', ' ' }, StringSplitOptions.RemoveEmptyEntries),
NeedsTranslation = request.NeedsTranslation,
Parameters = request.Parameters
};
if (request.Schedule != null)
{
createRequest.StartAt = request.Schedule.StartAt;
createRequest.EndAt = request.Schedule.EndAt;
if (Enum.TryParse<ScheduleTypeId>(request.Schedule.ScheduleType, out ScheduleTypeId scheduleType))
{
createRequest.ScheduleType = scheduleType;
}
}
var response = await monitoringService.CreateMonitorAsync(createRequest);
if (response.Duplicate)
{
return BadRequest($"Subject Id [{request.SubjectId}] is already being monitored with monitor {response.MonitorId.Id}.");
}
else
{
return CreatedAtAction("Get",
new
{
queryMonitorId = response.MonitorId.Id,
subjectId = request.SubjectId
},
JsonConvert.SerializeObject(
new
{
queryMonitorId = response.MonitorId.Id,
subjectId = request.SubjectId
}));
}
}
catch (RequiredFieldException rfe)
{
return BadRequest(rfe.Message);
}
catch (Exception ex)
{
logger.LogError($"[{Request.Path.Value}]: {ex.ToString()}");
return new StatusCodeResult(500);
}
}
private ClientId GetClientId()
{
var client_id = int.Parse(HttpContext.User.Claims.First(c => c.Type == TokenProviderOptions.ClientIdClaimName).Value);
return new ClientId(client_id);
}
private async Task<MonitorDto> GetByMonitorId(Guid monitorId)
{
return await monitoringService.GetMonitorByIdAsync(new MonitorId(monitorId));
}
private async Task<MonitorDto> GetBySubjectId(string id)
{
return await monitoringService.GetMonitorBySubjectIdAsync(GetClientId(), id);
}
}
}
Here is the CreateMonitorRequest
class code:
namespace Cantrel.Application.Contracts.Monitoring.Requests
{
[DataContract]
public class CreateMonitorRequest
{
public CreateMonitorRequest();
[DataMember]
public ClientId ClientId { get; set; }
[DataMember]
public BillingCodeId BillingCodeId { get; set; }
[DataMember]
public string SubjectId { get; set; }
[DataMember]
public ScheduleTypeId? ScheduleType { get; set; }
[DataMember]
public DateTime? StartAt { get; set; }
[DataMember]
public DateTime? EndAt { get; set; }
[DataMember]
public string Subject { get; set; }
[DataMember]
public List<string> Aliases { get; set; }
[DataMember]
public string[] Categories { get; set; }
[DataMember]
public bool NeedsTranslation { get; set; }
[DataMember]
public Dictionary<string, string> Parameters { get; set; }
}
}
Here is the MonitorDTO
class code:
namespace Cantrel.Application.Contracts.Monitoring.Dtos
{
[DataContract]
public class MonitorDto
{
[DataMember]
public MonitorId Id { get; set; }
[DataMember]
public ClientId ClientId { get; set; }
[DataMember]
public BillingCodeId BillingCodeId { get; set; }
[DataMember]
public string SubjectId { get; set; }
[DataMember]
public DateTime CreatedAt { get; set; }
[DataMember]
public MonitorScheduleDto Schedule { get; set; }
[DataMember]
public MonitorSearchDto Search { get; set; }
[DataMember]
public MonitorStatus Status { get; set; }
[DataMember]
public string SubjectTranslated { get; set; }
[DataMember]
public string AliasTranslated { get; set; }
}
}
I make the following call in Postman:
POST: {{api}}/Monitors
Body:
{
"clientid":"1",
"billingcodeid":"ABCDEFGH-1234-4567-8901-ABCDEFGHIJKL",
"subject": "John Doe",
"subjectId":"Test1234",
"ScheduleType" : "1",
"Categories": "GENER",
"parameters":
{"dateofbirth":"8/07/2018"}
}
Per the code, the expected return is a Guid which represents the newly-created MonitorId. Instead, I receive a 500 Internal Server Error
with no details. However, the record is successfully created in the database. (SQL Server 2016).
The log shows this as the error:
[ERR] Connection id ""0HLGC42CD408N"", Request id ""0HLGC42CD408N:00000004"": An unhandled exception was thrown by the application. (560e7d32)
System.InvalidOperationException: No route matches the supplied values.
at Microsoft.AspNetCore.Mvc.CreatedAtActionResult.OnFormatting(ActionContext context)
at Microsoft.AspNetCore.Mvc.Internal.ObjectResultExecutor.ExecuteAsync(ActionContext context, ObjectResult result)
at Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.<InvokeResultAsync>d__19.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
I have another Controller in this project with very similar syntax and all is working well with it. I don't see any differences between the two that would indicate to me why this one doesn't work.
What could be causing this error, and how is the record still getting created in the database?
EDIT: added additional code for proper context to contribute to the answer provided by @anserk
Upvotes: 3
Views: 8929
Reputation: 1340
The CreatedAtAction
returns an URI to the entity you just created. In your controller you didn't define the action ("GET") for getting the resource. You should add a new action for it.
DTO:
public class MonitorDto
{
public string QueryMonitorId {get; set;},
public string SubjectId {get; set;}
}
In your controller:
...
[HttpGet("{queryMonitorId}/{subjectId}", Name = "Get")]
public async Task<IActionResult> GetMonitor(string queryMonitorId, string subjectId)
{
...
// Your get implementation here.
Ok(new MonitorDto { QueryMonitorId = queryMonitorId, SubjectId = subjectId });
}
More information about https://learn.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.mvc.controllerbase.createdataction?view=aspnetcore-2.1
Upvotes: 7