RedirectToRoute Not Working - c#

Environment:
I am working in WebAPI. I implemented all CRUD methods.
Scenario:
In PUT method, i am excluding some properties like 'CreatedDate' to be updated. My Code like below;
[Route("")]
public async Task<IHttpActionResult> Put(MyModel myModel)
{
//Parsing to Business Entity
var item = TheFactory.Parse(myModel);
bool result = await _myBL.UpdateLab(item);
if (!result)
{
return BadRequest("Error in Save");
}
//Converting to view model and return
return Ok(TheFactory.Create(item));
}
Problem:
Here i am not getting CreatedDate from user. So it always min date.In DAL i specified this field as not modified so it always have the created data value. In return i am converting to viewmodel from the model user given. So CreatedDate is min date not original.
MySolution:
So to avoid that get the updated object from the Database again and return to the user. I already have get method to return the object by passing id. So i just changed my PUT method like below;
[Route("")]
public async Task<IHttpActionResult> Put(MyModel myModel)
{
//Parsing to Business Entity
var item = TheFactory.Parse(myModel);
bool result = await _myBL.UpdateLab(item);
if (!result)
{
return BadRequest("Error in Save");
}
//Redirect to getbyid method
return RedirectToRoute("GetById", new { id = item.Id });
}
Issue:
Here i thought i can reuse the method get method so it also returns ok(object). But after changed to this I got the following exception;
The requested resource does not support http method 'PUT'.
The response status is 405. Please help me to find the routecause

You need to just call GetById(item.id)
[Route("")]
public async Task<IHttpActionResult> Put(MyModel myModel)
{
//Parsing to Business Entity
var item = TheFactory.Parse(myModel);
bool result = await _myBL.UpdateLab(item);
if (!result)
{
return BadRequest("Error in Save");
}
return GetById(item.Id);
}

Related

How to a Pass Parameter from Endpoint to API Controller

I'm new to programing and have been following a tutorial. I have been able to pass a parameter from my View to my Endpoint...as well as from my API Controller to the SQL Server Stored Procedure. The Parameter will get to the Endpoint but isn't passed to the controller...if I manually pass that parameter through when debugging, it will return the correct data to the View. So what I can't seem to figure out is how to pass the parameter from the Endpoint to the controller.
Endpoint:
public async Task<List<GroupModel>> GetGroupById(int groupId)
{
using (HttpResponseMessage response = await _apiHelper.ApiClient.GetAsync($"/api/FeedGroup/GetGroupById/{groupId}")
{
if (response.IsSuccessStatusCode)
{
var result = await response.Content.ReadAsAsync<List<GroupModel>>();
return result;
}
else
{
throw new Exception(response.ReasonPhrase);
}
}
API Controller:
[HttpGet("GetGroupById/{GroupId:int}")]
[Route("GetGroupById")]
public List<GroupModel> GetGroupById(int GroupId)
{
int groupId = GroupId;
return _GroupData.GetGroupById(groupId);
}
Also for info, you are calling an Asynchronous method in your controller over a synchronous HttpGet method here. Thats not a good solution.
A better solution would be the following:
[HttpGet]
[Route("GetGroupById")]
public async Task<List<GroupModel>> GetGroupById(int GroupId)
{
int groupId = GroupId;
return await _GroupData.GetGroupById(groupId);
}

How can I show mi async Task<object> return into my View?

I'm pretty new with ASP.Net Core and I'm trying to show my async Task Filter return into my View but not sure how to...
This is my Controller code:
public class OrderNumberController : Controller
{
public async Task<IActionResult> Index(string orderNumber)
{
string[] locales = HPMSConstants.System.SupportedLocales.OrderBy(l => l).ToArray();
ViewBag.Locales = locales;
return View();
}
[HttpPost]
public async Task<PaymentGatewayTransactionManagerService.PaymentGatewayRecord> Filter(string orderNumber)
{
var result = await GetOrders(orderNumber);
return result;
}
private async Task<PaymentGatewayTransactionManagerService.PaymentGatewayRecord> GetOrders(string orderNumber)
{
var httpBinding = new System.ServiceModel.BasicHttpBinding();
// var address = new System.ServiceModel.EndpointAddress("http://pgtms.local.myherbalife.com/PaymentGatewayTransactionManager/Service.svc");
//var address = new System.ServiceModel.EndpointAddress("http://zus2q1ssb000000.hrbl.net:8330/Service.svc");
var address = new System.ServiceModel.EndpointAddress("http://zuswqa4svc01:8330/Service.svc");
PaymentGatewayTransactionManagerClient clientService = new PaymentGatewayTransactionManagerClient(httpBinding, address);
var result = await clientService.GetPaymentGatewayRecordAsync(orderNumber);
return result;
}
I think I see what you are asking. In the second two methods you are returning a Task<something> where the "something" is the result that you got after awaiting the result from an async method call. The first method is showing an MVC view controller method returning a view. It turns out that method is coded wrong. It should be (ignoring the first two lines which seem unrelated to the problem)
public IActionResult Index(string orderNumber)
{
string[] locales = HPMSConstants.System.SupportedLocales.OrderBy(l => l).ToArray();
ViewBag.Locales = locales;
return View();
}
This you don't need the async Task<IActionResult> because you are not awaiting any asynchronous methods within the method shown. View() is a method on the Microsoft.AspNetCore.Mvc.Controller which is not asynchronous and returns a Microsoft.AspNetCore.Mvc.ViewResult, which extends a ActionResult.
So the short answer is, just make sure your method returns the values it says it does in the method signature and make sure not to ignore the warnings and errors in Visual Studio or Visual Code. There is a warning if you add async to a method but you do not await anything in the body. There is an error if you try to return a Task<IActionResult> but are returing something that implements IActionResult instead.

How to get the Values from a Task<IActionResult> returned through an API for Unit Testing

I have created an API using ASP.NET MVC Core v2.1. One of my HttpGet methods is set up as follows:
public async Task<IActionResult> GetConfiguration([FromRoute] int? id)
{
try
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
..... // Some code here
return Ok(configuration);
}
catch (Exception ex)
{
... // Some code here
}
}
When unit testing this I can check that Ok was the response, but I really need to see the values of the configuration. I don't seem to be able to get this to work with the following:
[TestMethod]
public void ConfigurationSearchGetTest()
{
var context = GetContextWithData();
var controller = new ConfigurationSearchController(context);
var items = context.Configurations.Count();
var actionResult = controller.GetConfiguration(12);
Assert.IsTrue(true);
context.Dispose();
}
At runtime, I can check that actionResult has certain values that I am unable to code for. Is there something I am doing wrong? Or am I just thinking about this wrong? I would like to be able to do:
Assert.AreEqual(12, actionResult.Values.ConfigurationId);
You can get tested controller without changing returned type.
IActionResult is base type for all others.
Cast result into expected type and compare returned value with expected.
Since you are testing asynchronous method, make test method asynchronous as well.
[TestMethod]
public async Task ConfigurationSearchGetTest()
{
using (var context = GetContextWithData())
{
var controller = new ConfigurationSearchController(context);
var items = context.Configurations.Count();
var actionResult = await controller.GetConfiguration(12);
var okResult = actionResult as OkObjectResult;
var actualConfiguration = okResult.Value as Configuration;
// Now you can compare with expected values
actualConfuguration.Should().BeEquivalentTo(expected);
}
}
Good practice would suggest that you don't have a lot of code in your controller actions to test and the bulk of logic is in decoupled objects elsewhere that are much easier to test. Having said that, if you still want to test your controllers then you need to make your test async and await the calls.
One of the problems you will have is that you are using IActionResult as it allows you to return BadRequest(...) and Ok(...). However, since you are using ASP.NET MVC Core 2.1, you may want to start using the new ActionResult<T> type instead. This should help with your testing because you can now get direct access to the strongly typed return value. For example:
//Assuming your return type is `Configuration`
public async Task<ActionResult<Configuration>> GetConfiguration([FromRoute] int? id)
{
try
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
..... // Some code here
// Note we are now returning the object directly, there is an implicit conversion
// done for you
return configuration;
}
catch (Exception ex)
{
... // Some code here
}
}
Note we are now returning the object directly as there is an implicit conversion from Foo to ActionResult<Foo>
Now your test can look like this:
[TestMethod]
public async Task ConfigurationSearchGetTest()
{
var context = GetContextWithData();
var controller = new ConfigurationSearchController(context);
var items = context.Configurations.Count();
// We now await the call
var actionResult = await controller.GetConfiguration(12);
// And the value we want is now a property of the return
var configuration = actionResult.Value;
Assert.IsTrue(true);
context.Dispose();
}
As my reputation does not allow me to comment on #DavidG answer which goes in the right direction, I will put a sample on how to get the value inside Task<IActionResult>.
As # Christopher J. Reynolds pointed out, actionResult.Value can be seen at runtime but not at compilation.
So, I'll show a basic test in which get the Values:
[TestMethod]
public async Task Get_ReturnsAnArea()
{
// Arrange
string areaId = "SomeArea";
Area expectedArea = new Area() { ObjectId = areaId, AreaNameEn = "TestArea" };
var restClient = new Mock<IRestClient>();
restClient.Setup(client => client.GetAsync<Area>(It.IsAny<string>(), false)).ReturnsAsync(expectedArea);
var controller = new AreasController(restClient.Object);
//// Act
// We now await the call
IActionResult actionResult = await controller.Get(areaId);
// We cast it to the expected response type
OkObjectResult okResult = actionResult as OkObjectResult;
// Assert
Assert.IsNotNull(okResult);
Assert.AreEqual(200, okResult.StatusCode);
Assert.AreEqual(expectedArea, okResult.Value);
// We cast Value to the expected type
Area actualArea = okResult.Value as Area;
Assert.IsTrue(expectedArea.AreaNameEn.Equals(actualArea.AreaNameEn));
}
For sure this could be improved but I just wanted to show you a simple way to get it.
I hope it helps.
You need to await the call to GetConfiguration to get the IActionResult object back as follows:
var actionResult = await controller.GetConfiguration(12);
To do this you need to change the signature of your test method to be async as well. So change this:
public void ConfigurationSearchGetTest()
To this:
public async Task ConfigurationSearchGetTest()
If you need fast solution,
use JsonConvert.SerializeObject() and after that JsonConvert.DeserializeObject() then you will get Object with values.
[TestMethod]
public async Task ConfigurationSearchGetTest()
{
using (var context = GetContextWithData())
{
var controller = new ConfigurationSearchController(context);
var items = context.Configurations.Count();
var actionResult = await controller.GetConfiguration(12);
var okResult = actionResult as OkObjectResult;
var actualConfiguration = okResult.Value ;
//
//IMPORTANT ONLY BELOW two lines need.
//
var actualConfigurationJStr=JsonConvert.SerializeObject( okResult.Value );
var hereObjectWithStrongType=JsonConvert.DeserializeObject<Configuration>(actualConfigurationJStr);
// Now you can compare with expected values
actualConfuguration.Should().BeEquivalentTo(expected);
}
}

Await overriding previous variable in asynchronous method, multiple await

public async Task<ActionResult> Print(BasicSurveyReportViewModel paramModel)
{
ActionResult OverallSummary =
await basicSurveyReportController.OverallSummary(paramModel);
ActionResult CompanyInfo =
await basicSurveyReportController.CompanyInfo(paramModel);
ViewBag.OverallSummary = OverallSummary;
ViewBag.CompanyInfo = CompanyInfo;
return View();
}
In this code, first OverallSummary variable is set successfully.
But when running at the CompanyInfo line's method, it puts the variable into both CompanyInfo and OverallSummary.
How do I can get the results into separate variables?
It looks like you're trying to call asynchronous child actions, which are not supported in ASP.NET 4.x. I suspect that the ASP.NET controller is just accessing the current request when you return View(blah) or whatever from your child actions.
The only way to correctly do this (in the current version of ASP.NET) is to write helper methods that do the actual work and return your own types (not ActionResult):
OverallSummary GetOverallSummaryAsync(BasicSurveyReportViewModel model);
CompanyInfo GetCompanyInfoAsync(BasicSurveyReportViewModel model);
public async Task<ActionResult> Print(BasicSurveyReportViewModel paramModel)
{
OverallSummary overallSummary =
await GetOverallSummaryAsync(paramModel);
CompanyInfo companyInfo =
await GetCompanyInfoAsync(paramModel);
ViewBag.OverallSummary = overallSummary;
ViewBag.CompanyInfo = companyInfo;
return View();
}
Then your other controller actions can convert to use those methods as well, like this:
public async Task<ActionResult> OverallSummary(BasicSurveyReportViewModel paramModel)
{
return View(await GetOverallSummaryAsync(paramModel));
}

Return an object along with a 409 Conflict error in a Web API 2 POST call backed by Entity Framework?

I have a C# Entity Framework Web API 2 controller. Currently when an attempt is made via the POST method to create an object with the same text for the main text field, I return a 409 Conflict error as an StatusCode result to indicate the addition is considered a duplicate.
What I'd like to do is return the server side object that triggered the duplicate error too. So I need something akin to the Ok() method but a variant that returns a 409 Conflict error as the HTTP status code instead of an HTTP OK status code.
Is there such a thing? How can I do this? If I can make this work the client doesn't have to do a subsequent Get call to the server to get the existing object after receiving a 409 Conflict error.
Here's the current POST method:
public IHttpActionResult PostCanonical(Canonical canonical)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
// Check for duplicate Canonical text for the same app name.
if (db.IsDuplicateCanonical(canonical.AppName, canonical.Text))
{
// It's a duplicate. Return an HTTP 409 Conflict error to let the client know.
return StatusCode(HttpStatusCode.Conflict);
}
db.CanonicalSentences.Add(canonical);
db.SaveChanges();
return CreatedAtRoute("DefaultApi", new { id = canonical.ID }, canonical);
}
You should return Content:
return Content(HttpStatusCode.Conflict, original);
Content is method on the ApiController class which will create a NegotiatedContentResult with the provided HttpStatusCode and content. There is no need to create your own extension method on the ApiController class like in the accepted answer.
Arrived here looking for help with ASP.NET Core HTTP 409 - this is related, just the newer approach to solving this same problem.
Conflict ActionResult
return Conflict(new { message = $"An existing record with the id '{id}' was already found."});
EDIT: This solution is for WebApi prior v5, please see this answer if you are using v5 or above.
You could return a NegotiatedContentResult<T> that lets you specify the status code and an object to be put into the http message body.
Change your code to something like this:
public IHttpActionResult PostCanonical(Canonical canonical)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
// Check for duplicate Canonical text for the same app name.
if (db.IsDuplicateCanonical(canonical.AppName, canonical.Text))
{
// It's a duplicate. Return an HTTP 409 Conflict error to let the client know.
var original = db.CanonicalSentences.First(c => c.ID == canonical.ID);
return new NegotiatedContentResult<T>(HttpStatusCode.Conflict, original, this);
}
db.CanonicalSentences.Add(canonical);
db.SaveChanges();
return CreatedAtRoute("DefaultApi", new { id = canonical.ID }, canonical);
}
Or maybe wrap it an extension method like this:
public static class HttpActionResultExtensions {
public static IHttpActionResult StatusCodeWithContent<T>(this ApiController #this, HttpStatusCode statusCode, T content) {
return new NegotiatedContentResult<T>(statusCode, content, #this);
}
}
And then use the extension like this:
public IHttpActionResult PostCanonical(Canonical canonical)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
// Check for duplicate Canonical text for the same app name.
if (db.IsDuplicateCanonical(canonical.AppName, canonical.Text))
{
// It's a duplicate. Return an HTTP 409 Conflict error to let the client know.
var original = db.CanonicalSentences.First(c => c.ID == canonical.ID);
return StatusCodeWithContent(HttpStatusCode.Conflict, original)
}
db.CanonicalSentences.Add(canonical);
db.SaveChanges();
return CreatedAtRoute("DefaultApi", new { id = canonical.ID }, canonical);
}
I had a similar issue with ASP.NET Core 1.0 RC2, however I encountered a DbUpdateConcurrencyException, using optimistic concurrency I did not want to allow my user to update an object that was already updated.
I also wanted to return the updated object to the user making the call. I'm able to create a new CreatedAtAction and set the StatusCode to StatusCodes.Status409Conflict
context.Entry(user).State = EntityState.Modified;
try
{
await context.SaveChangesAsync();
}
catch (DbUpdateConcurrencyException)
{
if (!UserExists(id))
{
return NotFound();
}
else
{
//remove the user we just added, otherwise it will not goto
//the database to obtain the updated user
context.Entry(user).State = EntityState.Detached;
var updatedUser = await context.Users.SingleOrDefaultAsync(m => m.Id == id);
if (updatedUser == null)
{
return NotFound();
}
var returnAction = CreatedAtAction("PutUser", new { id = user.Id }, updatedUser);
returnAction.StatusCode = StatusCodes.Status409Conflict;
return returnAction;
}
}

Categories