C# Web API Response Handling using List<> - c#

I have a POST method which will return the list of items from the user and since I am very new to c# web api, I am having a hardtime putting the right condition and response if the Id is null, empty or invalid. I've tried similar response and it doesn't work mainly because those examples are using iHttpActionResult instead of List<>
here is the code in my controller which I am not sure what to place on the comments I provided:
[HttpPost]
public List<ValueStory> UserValueStories ([FromBody] ValueStory valuestory)
//public void UserValueStories([FromBody] ValueStory Id)
{
if (valuestory.Id == "" || valuestory.Id == null)
{
//what code to add to change status code to 400 and to display error message?
}
//what code to put if the id is not valid, what status code and what message?
var valueStoryName = (from vs in db.ValueStories
where vs.Id == valuestory.Id
select vs).ToList();
List<ValueStory> vs1 = new List<ValueStory>();
foreach (var v in valueStoryName)
{
vs1.Add(new ValueStory()
{
Id = v.Id,
ValueStoryName = v.ValueStoryName,
Organization = v.Organization,
Industry = v.Industry,
Location = v.Location,
AnnualRevenue = v.AnnualRevenue,
CreatedDate = v.CreatedDate,
ModifiedDate = v.ModifiedDate,
MutualActionPlan = v.MutualActionPlan,
Currency = v.Currency,
VSId = v.VSId
});
}
return vs1.ToList();
}
Appreciate some help and some directions on how to do this correctly.

Change your return type to IHttpActionResult.
To return a 400 BAD REQUEST, return BadRequest().
To return a 404 NOT FOUND, return NotFound().
To return your list data, return Ok(vs1).
See the documentation for more information.
Optional: If you are using a documentation tool like Swagger or the Web Api Help Pages, also add the [ResponseType(typeof(List<ValueStory>))] attribute to your action method.

Method would need to be updated to allow for that level of flexibility
[HttpPost]
[ResponseType(typeof(List<ValueStory>))]
public IHttpActionResult UserValueStories ([FromBody] ValueStory valuestory) {
if (valuestory.Id == "" || valuestory.Id == null) {
//what code to add to change status code to 400 and to display error message?
return BadRequest("error message");
}
var valueStoryName = (from vs in db.ValueStories
where vs.Id == valuestory.Id
select vs).ToList();
var vs1 = new List<ValueStory>();
foreach (var v in valueStoryName) {
vs1.Add(new ValueStory() {
Id = v.Id,
ValueStoryName = v.ValueStoryName,
Organization = v.Organization,
Industry = v.Industry,
Location = v.Location,
AnnualRevenue = v.AnnualRevenue,
CreatedDate = v.CreatedDate,
ModifiedDate = v.ModifiedDate,
MutualActionPlan = v.MutualActionPlan,
Currency = v.Currency,
VSId = v.VSId
});
}
return Ok(vs1);
}

If you really want to keep your return data type (which I think you shouldn't, do as stated in the other answer), then you can throw exceptions as described in Exception Handling in ASP.NET Web API:
To throw a simple exception with specific HTTP code use:
throw new HttpResponseException(HttpStatusCode.NotFound);
To specify message you can do as follows:
var resp = new HttpResponseMessage(HttpStatusCode.NotFound)
{
Content = new StringContent(string.Format("No product with ID = {0}", id)), ReasonPhrase = "Product ID Not Found"
}
throw new HttpResponseException(resp);

As per the knowledge I have on Web API, in the POST method you have to return the result of the post call instead(or along with) of List.
Its better to create a new model which will store the data (List) and result of the POST call (error message and status code).
Based on the Id, you can add respective error message and code.
In case of invalid data, you can make data as null.
The model may look like this.
class Model{
string errorMsg,
string statusCode,
List<ValueStory> data
}

Related

How to test a controller POST method which returns no data in response content in .NET Core 3.1?

i am new to integration tests. I have a controller method which adds a user to the database, as shown below:
[HttpPost]
public async Task<IActionResult> CreateUserAsync([FromBody] CreateUserRequest request)
{
try
{
var command = new CreateUserCommand
{
Login = request.Login,
Password = request.Password,
FirstName = request.FirstName,
LastName = request.LastName,
MailAddress = request.MailAddress,
TokenOwnerInformation = User
};
await CommandBus.SendAsync(command);
return Ok();
}
catch (Exception e)
{
await HandleExceptionAsync(e);
return StatusCode(StatusCodes.Status500InternalServerError,
new {e.Message});
}
}
As you have noticed my method returns no information about the user which has been added to the database - it informs about the results of handling a certain request using the status codes. I have written an integration test to check is it working properly:
[Fact]
public async Task ShouldCreateUser()
{
// Arrange
var createUserRequest = new CreateUserRequest
{
Login = "testowyLogin",
Password = "testoweHaslo",
FirstName = "Aleksander",
LastName = "Kowalski",
MailAddress = "akowalski#onet.poczta.pl"
};
var serializedCreateUserRequest = SerializeObject(createUserRequest);
// Act
var response = await HttpClient.PostAsync(ApiRoutes.CreateUserAsyncRoute,
serializedCreateUserRequest);
// Assert
response
.StatusCode
.Should()
.Be(HttpStatusCode.OK);
}
I am not sure is it enough to assert just a status code of response returned from the server. I am confused because, i don't know, shall i attach to assert section code, which would get all the users and check does it contain created user for example. I don't even have any id of such a user because my application finds a new id for the user while adding him/her to the database. I also have no idea how to test methods like that:
[HttpGet("{userId:int}")]
public async Task<IActionResult> GetUserAsync([FromRoute] int userId)
{
try
{
var query = new GetUserQuery
{
UserId = userId,
TokenOwnerInformation = User
};
var user = await QueryBus
.SendAsync<GetUserQuery, UserDto>(query);
var result = user is null
? (IActionResult) NotFound(new
{
Message = (string) _stringLocalizer[UserConstants.UserNotFoundMessageKey]
})
: Ok(user);
return result;
}
catch (Exception e)
{
await HandleExceptionAsync(e);
return StatusCode(StatusCodes.Status500InternalServerError,
new {e.Message});
}
}
I believe i should somehow create a user firstly in Arrange section, get it's id and then use it in Act section with the GetUserAsync method called with the request sent by HttpClient. Again the same problem - no information about user is returned, after creation (by the way - it is not returned, because of my CQRS design in whole application - commands return no information). Could you please explain me how to write such a tests properly? Have i missed anything? Thanks for any help.
This is how I do it:
var response = (CreatedResult) await _controller.Post(createUserRequest);
response.StatusCode.Should().Be(StatusCodes.Status201Created);
The second line above is not necessary, just there for illustration.
Also, your response it's better when you return a 201 (Created) instead of the 200(OK) on Post verbs, like:
return Created($"api/users/{user.id}", user);
To test NotFound's:
var result = (NotFoundObjectResult) await _controller.Get(id);
result.StatusCode.Should().Be(StatusCodes.Status404NotFound);
The NotFoundObjectResult assumes you are returning something. If you are just responding with a 404 and no explanation, replace NotFoundObjectResult with a NotFoundResult.
And finally InternalServerErrors:
var result = (ObjectResult) await _controller.Get(id);
result.StatusCode.Should().Be(StatusCodes.Status500InternalServerError);
You can use integrationFixture for that using this NuGet package. This is an AutoFixture alternative for integration tests.
The documented examples use Get calls but you can do other calls too. Logically, you should test for the status code (OkObjectResult means 200) value and the response (which could be an empty string, that is no problem at all).
Here is the documented example for a normal Get call.
[Fact]
public async Task GetTest()
{
// arrange
using (var fixture = new Fixture<Startup>())
{
using (var mockServer = fixture.FreezeServer("Google"))
{
SetupStableServer(mockServer, "Response");
var controller = fixture.Create<SearchEngineController>();
// act
var response = await controller.GetNumberOfCharacters("Hoi");
// assert
var request = mockServer.LogEntries.Select(a => a.RequestMessage).Single();
Assert.Contains("Hoi", request.RawQuery);
Assert.Equal(8, ((OkObjectResult)response.Result).Value);
}
}
}
private void SetupStableServer(FluentMockServer fluentMockServer, string response)
{
fluentMockServer.Given(Request.Create().UsingGet())
.RespondWith(Response.Create().WithBody(response, encoding: Encoding.UTF8)
.WithStatusCode(HttpStatusCode.OK));
}
In the example above, the controller is resolved using the DI described in your Startup class.
You can also do an actual REST call using using Refit. The application is self hosted inside your test.
using (var fixture = new RefitFixture<Startup, ISearchEngine>(RestService.For<ISearchEngine>))
{
using (var mockServer = fixture.FreezeServer("Google"))
{
SetupStableServer(mockServer, "Response");
var refitClient = fixture.GetRefitClient();
var response = await refitClient.GetNumberOfCharacters("Hoi");
await response.EnsureSuccessStatusCodeAsync();
var request = mockServer.LogEntries.Select(a => a.RequestMessage).Single();
Assert.Contains("Hoi", request.RawQuery);
}
}

How to retain data gained from multiple requests in EF Core?

I have asp.net core web api with React client. I'm adding data through my user interface created in React. In my api, Db context is added as scoped service, and each time my request finishes and new one is started, all my data from previous request is lost.
This is how my Configure services looks like:
services.AddDbContext<TicketingContext>(o=>o.UseLazyLoadingProxies().UseSqlServer(connectionString));
Controller method for posting data looks like this:
[HttpPost("{id}/tickets")]
public IActionResult CreateNewTicket(string id,
[FromBody] TicketForCreationDto ticketForCreation)
{
if (ticketForCreation == null)
{
return BadRequest();
}
var ticketEntity = _mapper.Map<Ticket>(ticketForCreation);
_ticketRepository.AddNewTicket(ticketEntity);
_ticketRepository.AddTicketToClient(id, ticketEntity);
if (!_ticketRepository.Save())
{
throw new Exception("Creating ticket failed on save");
}
var ticketToReturn = _mapper.Map<TicketDto>(ticketEntity);
return CreatedAtRoute("GetTicket", new {id=id, ticketId = ticketToReturn.Id }, ticketToReturn);
}
and methods in repository like this:
AddNewTicket:
public void AddNewTicket(Ticket ticket)
{
if (ticket.Id == Guid.Empty)
{
ticket.Id = Guid.NewGuid();
}
var dispatcher = AssignTicketToDispatcher(ticket);
if (dispatcher == null)
{
throw new Exception("There are no dispatchers matching this ticket");
}
dispatcher.UserTickets.Add(new UserTicket()
{
IdentityUser = dispatcher,
Ticket = ticket,
UserId = dispatcher.Id,
TicketId = ticket.Id
});
_context.Tickets.Add(ticket);
}
AddTicketToClient:
public void AddTicketToClient(string id, Ticket ticket)
{
var client = _identityUserRepository.GetClient(id);
if (client == null)
{
client = _context.Users.Where(u => u.UserName == "username").FirstOrDefault();
}
client.UserTickets.Add(new UserTicket()
{
IdentityUser = client,
Ticket = ticket,
UserId = client.Id,
TicketId = ticket.Id
});
}
Save:
public bool Save()
{
return (_context.SaveChanges() >= 0);
}
I want to be able to store data gained through multiple requests.
Does anyone have idea how to do that?
Use the database as it's the best method you have for persisting your data.
So When you do a request - at the end of the request, after your latest data is saved - query for the data from previous requests that you need and return it.
e.g. retrieve the last 5 requests saved newest first (where id is example for your primary key field):
var latestSaved = _context.UserTickets.OrderByDescending(x => x.id).Take(5);
Or amend to return all relevant data for e.g. active user by passing a user id stored client side.
Pass through any params you need to request the relevant data.
Use joins / includes set up in your entities. Whatever you need to do - make use of your entity relationships to get what you need from you database. Why try and replicate what it already does? :)

returning collection (list) in c# as Json

I have a method like this:
[HttpPost]
[ActionName("GetUserData")]
public ActionResult GetUserData()
{
using (var ctx = new myEntities())
{
ctx.Configuration.LazyLoadingEnabled = false;
ctx.Configuration.ProxyCreationEnabled = false;
var user = ctx.Users.Include("UserRoles").FirstOrDefault(x => x.UserId == 4);
ctx.Configuration.LazyLoadingEnabled = true;
ctx.Configuration.ProxyCreationEnabled = true;
return Json(new
{
Email = user.Email,
Roles = user.UserRoles
}, JsonRequestBehavior.AllowGet);
}
}
The post is done via jQuery like this:
$.post("/Administrator/GetUserData", function (data) {
console.log(data);
});
I'm trying to write out the returned data, but the console is showing me Internal Error 500 when I write the code like above...
In other case when returned result is like this:
return Json(new
{
Email = user.Email
// returning just email for example to see in console..
},JsonRequestBehavior.AllowGet);
Returning just email as a plain simple string works okay, but when I try to return the User's roles as an array via JSON , then I get problems like above...
The collection UserRoles is of Type ICollection...
What am I doing wrong here?
P.S. Guys I dug out the exception, it's like following:
Exception Details: System.InvalidOperationException: A circular reference was detected while serializing an object of type 'System.Collections.Generic.HashSet`1[[MyModel.Models.DatabaseConnection.UserRoles, MyEntity, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]]'.
it’s clear that your model of role contains a property point to user which causes the issue.
you should select an anonymous object to return
Roles = user.Roles.Select(r=> new { name = r.Name }).ToArray();

Web API Restful Service C# Perform SQL Query

Working on a web api restful service.
I have a table called tests. The primary key is auto-increment. Each row contains a username and a test question. This DB is not really optimised, but it doesn't need to be.
Because the primary key is just an int to keep each row unique, there are many rows with duplicate usernames. This is fine.
I want to be able to return all rows with a matching username.
I want to be able to do it with a get request with the url: www.mywebsite.com/api/tests/{username}
The default controller methods in Visual Studio are only able to search by primary key to return one unique result.
This is something like what I want, but it doesn't work. 500 error. I'm not knowledgeable on debugging either, so point me in the right direction there if possible so I can provide more info.
// GET: api/Tests/robert
[ResponseType(typeof(Test))]
public async Task<IHttpActionResult> GetTest(string id)
{
//This is the default approach that just searches by primary key
//Test test = await db.Tests.FindAsync(id);
/*This is roughly what I want. Compiles, but doesn't work.*/
var query = "SELECT* WHERE id=" + id;
Test test = db.Tests.SqlQuery(query, id);
if (test == null)
{
return NotFound();
}
return Ok(test);
}
Let me know where I screwed up. I've been blundering over this for hours. I don't claim to be particularly good at any of this.
Try declaring your method like this
[RoutePrefix("api")]
public class HisController : ApiController
{
[HttpGet]
public HttpResponseMessage Test(string name) {...}
}
Also, I would recommend You to use entity framework
[RoutePrefix("api")]
public class HisController : ApiController
{
[HttpGet]
public HttpResponseMessage Test(string name)
{
using (MyEntities bm = new MyEntities())
{
var usr = bm.Users.Where(u => u.Name == name ).ToList();
if (usr.Count() > 0)
return Request.CreateResponse(HttpStatusCode.OK, new
{
Success = true
,
Message = "Total users: " + usr.Count()
,
Data = usr
});
return Request.CreateResponse(HttpStatusCode.NotFound, new { Success = false, Message = "No users found..." });
}
}
}

Orchard CMS save MediaPickerField

I'm developing a web app based on Orchard.
I'm coding a module that manages Staff Users, this users are ContentTypes(Staff_User) composed of UserPart and StaffUserPart (Custom part, defined in a migration) -> this part has a MediaPickerField.
This is the code in my controller to show the creation template of a staff users
public ActionResult CreateStaff() {
IContent staffUser = _contentManager.New("Staff_User");
var model = _contentManager.BuildEditor(staffUser);
return View((object)model);
}
Ok, I have a template in EditorTemplates/Staff.cshtml . The MediaPicker field is attached by the the BuildEditor function (as a shape).
This is the Post controller:
public ActionResult CreateStaffPost(FormCollection input) {
IContent staffUser = _contentManager.New("Staff_User");
//UserPart validation
if (String.IsNullOrEmpty(input["user.Email"]))
ModelState.AddModelError("Email", "The Email field is required.");
//Check if user already exits
var oldUser = _contentManager.Query("User").Where<UserPartRecord>(x => x.Email == input["user.Email"])
.List()
.FirstOrDefault();
if (oldUser != null)
ModelState.AddModelError("Email", "That email adress is already registered.");
if (!ModelState.IsValid) {
var model = _contentManager.UpdateEditor(staffUser, this);
return View(model);
}
StaffUserPart staff = staffUser.As<StaffUserPart>();
staff.FirstName = input["FirstName"];
staff.LastName = input["LastName"];
staff.Location = input["Location"];
staff.JobTitle = input["JobTitle"];
staff.Summary = input["Summary"];
staff.AreaOfExpertise = input["AreaOfExperience"];
staff.Category = input["Category"];
staff.Experience = input["Experience"];
//Media picker field values
var staffImageField = (MediaPickerField)staff.Fields.Single(x => x.Name == "Photo");
//TODO Fix image save during creation
staffImageField.Url = input["StaffUserPart.Photo.Url"];
staffImageField.AlternateText = input["StaffUserPart.Photo.AlternateText"];
staffImageField.Class = input["StaffUserPart.Photo.Class"];
staffImageField.Style = input["StaffUserPart.Photo.Style"];
staffImageField.Alignment = input["StaffUserPart.Photo.Alignment"];
staffImageField.Width = String.IsNullOrEmpty(input["StaffUserPart.Photo.Width"]) ? 0 : Convert.ToInt32(input["StaffUserPart.Photo.Width"]);
staffImageField.Height = String.IsNullOrEmpty(input["StaffUserPart.Photo.Height"]) ? 0 : Convert.ToInt32(input["StaffUserPart.Photo.Height"]);
UserPart userPart = staffUser.As<UserPart>();
userPart.UserName = input["user.Email"];
userPart.Email = input["user.Email"];
userPart.NormalizedUserName = input["user.Email"].ToLowerInvariant();
userPart.Record.HashAlgorithm = "SHA1";
userPart.RegistrationStatus = UserStatus.Approved;
userPart.EmailStatus = UserStatus.Approved;
//Set Password
_membershipService.SetPassword(userPart.As<UserPart>(), input["password"]);
//Create the StaffUser
_contentManager.Create(staffUser);
return RedirectToAction("Index");
}
Question
This works but the MediaPickerField doesn;t save the data. I use the debugger to see if the values from input["StaffUserPart.Photo"] and the values are there.
Any ideas?
It looks like you're doing more work than you need to. If you move your call to UpdateEditor, this method will do the work of putting posted values into your content. You'll need to make sure you're implementing IUpdater. Also, I added a dependency on ITransactionManager. I'm hoping this will help catch something not getting put in the right spot.
public ActionResult CreateStaffPost(FormCollection input) {
IContent staffUser = _contentManager.New("Staff_User");
//Create the StaffUser
_contentManager.Create(staffUser);
//UserPart validation
if (String.IsNullOrEmpty(input["user.Email"]))
ModelState.AddModelError("Email", "The Email field is required.");
//Check if user already exits
var oldUser = _contentManager.Query("User").Where<UserPartRecord>(x => x.Email == input["user.Email"])
.List()
.FirstOrDefault();
if (oldUser != null)
ModelState.AddModelError("Email", "That email adress is already registered.");
//This does all the work of hydrating your model
var model = _contentManager.UpdateEditor(staffUser, this);
if (!ModelState.IsValid) {
_transactionManager.Cancel();
return View(model);
}
//Set Password
_membershipService.SetPassword(userPart.As<UserPart>(), input["password"]);
return RedirectToAction("Index");
}

Categories

Resources