REST API - CreatedAtRoute method doesn't return a value - rest

I'm building some REST API server in .NET Core and using Postman software to test it. I have a problem with POST method which doesn't return me any value ("Could not get any response") when I try to perform second Add operation on my DBContext class inside CreateUser method. My code :
UsersController :
[Produces("application/json")]
[Route("api/[controller]")]
public class UsersController : Controller
{
private readonly DBContext _context;
#region CONSTRUCTOR
public UsersController(DBContext context)
{
_context = context;
}
#endregion
#region HTTP GET
// GET: api/users || api/users?cardnr=xxx
[HttpGet]
public async Task<IActionResult> GetUsers(string cardNr)
{
if (String.IsNullOrEmpty(cardNr))
{
try
{
var users = await _context.Users.ToListAsync();
if (users.Any())
{
return Json(users);
}
else
{
return NotFound();
}
}
catch (Exception ex)
{
Helpers.ExceptionLogger.LogException(ex);
return StatusCode(500);
}
}
else
{
try
{
var user = await _context.Users.FirstOrDefaultAsync(u => u.Cards.Any(c => c.CardNumber.Equals(cardNr)));
if (user == null)
{
return NotFound();
}
else
{
return new ObjectResult(user);
}
}
catch (Exception ex)
{
Helpers.ExceptionLogger.LogException(ex);
return StatusCode(500);
}
}
}
//GET: api/users/1
[HttpGet("{id}", Name = "GetUserByID")]
public async Task<IActionResult> GetUserByID(Int32 id)
{
try
{
var user = await _context.Users.FirstOrDefaultAsync(u => u.IDUser == id);
if (user == null)
{
return NotFound();
}
else
{
return new ObjectResult(user);
}
}
catch (Exception ex)
{
Helpers.ExceptionLogger.LogException(ex);
return StatusCode(500);
}
}
#endregion
#region HTTP POST
[HttpPost]
public async Task<IActionResult> CreateUser([FromBody] Models.User userToCreate, string userGroupID)
{
if (userToCreate == null)
{
return BadRequest();
}
else
{
try
{
_context.Users.Add(userToCreate);
int parsingResult;
// if user passed userGroupID
if (userGroupID != null)
{
// parsing if userGroupID is a number
if (!int.TryParse(userGroupID, out parsingResult))
{
return BadRequest();
}
else
{
// if client want to assign a new user to some group
if (parsingResult > 0)
{
// creating new record in UserGroup table - assigning a user to group
var userGroup = new Models.UserGroup();
_context.Entry(userGroup).Property("IDGroup").CurrentValue = parsingResult;
_context.Entry(userGroup).Property("IDUser").CurrentValue = userToCreate.IDUser;
_context.UserGroups.Add(userGroup); // NOTE HERE
}
}
}
await _context.SaveChangesAsync();
return CreatedAtRoute("GetUserByID", new { id = userToCreate.IDUser }, userToCreate);
}
catch (Exception ex)
{
Helpers.ExceptionLogger.LogException(ex);
return StatusCode(500);
}
}
}
#endregion
}
User model :
public class User
{
[Key]
public int IDUser { get; set; }
[Required]
public string Name { get; set; }
public List<UserGroup> UsersGroups { get; set; }
}
UserGroup model :
public class UserGroup
{
public Group Group { get; set; }
public User User { get; set; }
}
DBContext class :
public class DBContext : DbContext
{
public DBContext(DbContextOptions<DBContext> options)
: base(options)
{
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
// shadow property - foreign key
modelBuilder.Entity<UserGroup>()
.Property<int>("IDUser");
// shadow property - foreign key
modelBuilder.Entity<UserGroup>()
.Property<int>("IDGroup");
modelBuilder.Entity<UserGroup>()
.HasKey( new string[]{ "IDUser", "IDGroup" });
modelBuilder.Entity<UserGroup>()
.HasOne(ug => ug.Group)
.WithMany(g => g.UsersGroups)
.HasForeignKey("IDGroup");
modelBuilder.Entity<UserGroup>()
.HasOne(ug => ug.User)
.WithMany(u => u.UsersGroups)
.HasForeignKey("IDUser");
base.OnModelCreating(modelBuilder);
}
public DbSet<Group> Groups { get; set; }
public DbSet<User> Users { get; set; }
public DbSet<UserGroup> UserGroups { get; set; }
}
The problem lies in HttpPost method in UsersController.
When I do "normal" POST and pass JSON object which contain a user to add without assigning it to group (empty userGroupID parameter) everything is ok - user gets added to the DataBase and Postman returns me a user with its ID.
screen :
and when I try to add a new user but with adding it to specific group I always get an error :
screen :
Even despite that error new user gets properly added to DB and associated with its group (record gets added to UserGroup table; UserGroup is join table between Users and Groups table). So I have proper data in my DB but I always get this error and I can't return new added user to client who called API and can't get his ID. Am I doing something wrong in my CreateUser method ?
UPDATE :
I have added a comment line in "NOTE HERE" in CreateUser method in UsersController. If I comment whole this line I don't get an error from Postman but obviously I don't get my User associated with its group (I don't get new record added to UserGroup join table). So it seems like another Add method on context object causing an error ... Does it make sense ?

Did you try to debug it?
Set a breakpoint on the row:
if (userToCreate == null)
Send again the request with Postman and debug your app. There you can see what and where it goes wrong.
Please let me know how it is going so I know how can I help you:)

Related

REST API Custom Response query

I am a bit new to REST API. I have the below controller created to mock an API Service called by a client code under Test. I need to return the response in JSON format as mentioned in the query and need some help to fix it.
Controller:
[HttpPost]
[ApiKeyAuth]
[ValidateModel]
[ProducesResponseType(typeof(Item), StatusCodes.Status201Created)]
public async Task<IActionResult> AddNewItem([FromBody] Item item)
{
var itemId = await _repo.AddItemAsync(item);
return CreatedAtAction(nameof(GetItemById), new { itemId, controller = "Example" },
itemId);
}
Contract:
public interface IExampleControllerRepository
{
Task<int> AddItemAsync(Item item);
}
Repo:
public class ExampleRepository : IExampleControllerRepository
{
private readonly ExampleDbContext _context;
public ExampleRepository(ExampleDbContext context) => _context = context;
public async Task<int> AddItemAsync(Item item)
{
_context.Add(item);
await _context.SaveChangesAsync();
return item.ItemId;
}
}
Expected positive response template:
{"response":{"status":0,"data":[{"id":"1234"}]}}
// Summary:
// Creates a Microsoft.AspNetCore.Mvc.CreatedAtActionResult object that produces
// a Microsoft.AspNetCore.Http.StatusCodes.Status201Created response.
//
// Parameters:
// actionName:
// The name of the action to use for generating the URL.
//
// routeValues:
// The route data to use for generating the URL.
//
// value:
// The content value to format in the entity body.
//
// Returns:
// The created Microsoft.AspNetCore.Mvc.CreatedAtActionResult for the response.
[NonAction]
public virtual CreatedAtActionResult CreatedAtAction(string? actionName, object? routeValues, [ActionResultObjectValue] object? value)
{
throw null;
}
The response body's template is related to the type of value, So if you want to get the response template like:
{"response":{"status":0,"data":[{"id":"1234"}]}}
You need to pass a value of a specific type instead of itemId.
Here is a simple demo.
public class Test
{
public List<response> responses { get; set; }
}
public class response
{
public int status { get; set; }
public List<test1> data { get; set; }
}
public class test1
{
public string Id { get; set; }
}
For testing convenience, I just hard code here.
[HttpPost]
[ApiKeyAuth]
[ValidateModel]
[ProducesResponseType(typeof(Item), StatusCodes.Status201Created)]
public async Task<IActionResult> AddNewItem([FromBody] Item item)
{
Test test = new Test()
{
responses = new List<response>()
{
new response()
{
status = 0,
data = new List<test1> {
new test1()
{
Id = "1234",
}
}
}
}
};
var itemId = await _repo.AddItemAsync(item);
return CreatedAtAction(nameof(GetItemById), new { Id = item.ItemId }, test);
}
Result

Asp.net core mvc migration database

When I add a column at Database, how to renew my DB.Context.
it is my Context.cs
modelBuilder.Entity<SensorModbusRtusetting>(entity =>
{
entity.ToTable("SensorModbusRTUSetting");
entity.Property(e => e.Id).HasColumnName("id");
entity.Property(e => e.Baudrate).HasColumnName("baudrate");
entity.Property(e => e.Channel)
.IsRequired()
.HasColumnType("jsonb")
.HasColumnName("channel");
entity.Property(e => e.Comport)
.IsRequired()
.HasColumnName("comport");
entity.Property(e => e.Pid).HasColumnName("pid");
entity.Property(e => e.Slaveid).HasColumnName("slaveid");
entity.Property(e => e.Nane).HasColumnName("nane");
It is my model
namespace Sensormanager2V2
{
public partial class SensorModbusRtusetting
{
public string Comport { get; set; }
public int Baudrate { get; set; }
public string Channel { get; set; }
public int Slaveid { get; set; }
public int Pid { get; set; }
public long Id { get; set; }
public string Nane { get; set; }
}
}
it is my controller
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Rendering;
using Microsoft.EntityFrameworkCore;
using Sensormanager2V2;
namespace Sensormanager2V2.Controllers
{
public class SensorModbusRtusettingsController : Controller
{
private readonly postgresContext _context;
public SensorModbusRtusettingsController(postgresContext context)
{
_context = context;
}
// GET: SensorModbusRtusettings
public async Task<IActionResult> Index()
{
return View(await _context.SensorModbusRtusettings.ToListAsync());
}
// GET: SensorModbusRtusettings/Details/5
public async Task<IActionResult> Details(long? id)
{
if (id == null)
{
return NotFound();
}
var sensorModbusRtusetting = await _context.SensorModbusRtusettings
.FirstOrDefaultAsync(m => m.Id == id);
if (sensorModbusRtusetting == null)
{
return NotFound();
}
return View(sensorModbusRtusetting);
}
// GET: SensorModbusRtusettings/Create
public IActionResult Create()
{
return View();
}
// POST: SensorModbusRtusettings/Create
// To protect from overposting attacks, enable the specific properties you want to bind to, for
// more details, see http://go.microsoft.com/fwlink/?LinkId=317598.
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Create([Bind("Comport,Baudrate,Channel,Slaveid,Pid,Id,Name")] SensorModbusRtusetting sensorModbusRtusetting)
{
if (ModelState.IsValid)
{
_context.Add(sensorModbusRtusetting);
await _context.SaveChangesAsync();
return RedirectToAction(nameof(Index));
}
return View(sensorModbusRtusetting);
}
// GET: SensorModbusRtusettings/Edit/5
public async Task<IActionResult> Edit(long? id)
{
if (id == null)
{
return NotFound();
}
var sensorModbusRtusetting = await _context.SensorModbusRtusettings.FindAsync(id);
if (sensorModbusRtusetting == null)
{
return NotFound();
}
return View(sensorModbusRtusetting);
}
// POST: SensorModbusRtusettings/Edit/5
// To protect from overposting attacks, enable the specific properties you want to bind to, for
// more details, see http://go.microsoft.com/fwlink/?LinkId=317598.
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Edit(long id, [Bind("Comport,Baudrate,Channel,Slaveid,Pid,Id,Name")] SensorModbusRtusetting sensorModbusRtusetting)
{
if (id != sensorModbusRtusetting.Id)
{
return NotFound();
}
if (ModelState.IsValid)
{
try
{
_context.Update(sensorModbusRtusetting);
await _context.SaveChangesAsync();
}
catch (DbUpdateConcurrencyException)
{
if (!SensorModbusRtusettingExists(sensorModbusRtusetting.Id))
{
return NotFound();
}
else
{
throw;
}
}
return RedirectToAction(nameof(Index));
}
return View(sensorModbusRtusetting);
}
// GET: SensorModbusRtusettings/Delete/5
public async Task<IActionResult> Delete(long? id)
{
if (id == null)
{
return NotFound();
}
var sensorModbusRtusetting = await _context.SensorModbusRtusettings
.FirstOrDefaultAsync(m => m.Id == id);
if (sensorModbusRtusetting == null)
{
return NotFound();
}
return View(sensorModbusRtusetting);
}
// POST: SensorModbusRtusettings/Delete/5
[HttpPost, ActionName("Delete")]
[ValidateAntiForgeryToken]
public async Task<IActionResult> DeleteConfirmed(long id)
{
var sensorModbusRtusetting = await _context.SensorModbusRtusettings.FindAsync(id);
_context.SensorModbusRtusettings.Remove(sensorModbusRtusetting);
await _context.SaveChangesAsync();
return RedirectToAction(nameof(Index));
}
private bool SensorModbusRtusettingExists(long id)
{
return _context.SensorModbusRtusettings.Any(e => e.Id == id);
}
}
}
If you update the structure of a table in your database, you need to ensure that your change is propagated to the data model, views, and controller.
For this tutorial, you will add a new column to the Student table to record the middle name of the student. To add this column, open the database project, and open the Student.sql file. Through either the designer or the T-SQL code, add a column named MiddleName that is an NVARCHAR(50) and allows NULL values.
Deploy this change to your local database by starting your database project (or F5). The new field is added to the table. If you do not see it in the SQL Server Object Explorer, click the Refresh button in the pane.
The new column exists in the database table, but it does not currently exist in the data model class. You must update the model to include your new column. In the Models folder, open the ContosoModel.edmx file to display the model diagram. Notice that the Student model does not contain the MiddleName property. Right-click anywhere on the design surface, and select Update Model from Database.
In the Update Wizard, select the Refresh tab and then select Tables > dbo > Student. Click Finish.
After the update process is finished, the database diagram includes the new MiddleName property. Save the ContosoModel.edmx file. You must save this file for the new property to be propagated to the Student.cs class. You have now updated the database and the model.
Build the solution.

Entity Framework 6 adding new many to many relationship

I'm using EF6 code first with MVC 5. I have two objects, Movie and User, with each object having a collection of the other (many-to-many). Using an existing User, I'm trying to associate that User to a Movie but no rows are being inserted into the database. The Movie could be existing or new, but either way the association is not being created.
Movie is just a simple POCO
User inherits from IdentityUser
public class User : IdentityUser {
public virtual ICollection<Movie> Movies { get; set; }
public User() {
Movies = new Collection<Movie>();
}
public async Task<ClaimsIdentity> GenerateUserIdentityAsync(UserManager<User> manager) {
var userIdentity = await manager.CreateIdentityAsync(this, DefaultAuthenticationTypes.ApplicationCookie);
return userIdentity;
}
}
My Controller Action:
public async Task<HttpResponseMessage> Post(Movie rawMovie) {
try {
var movie = _store.Movies.Get(m => m.Id == rawMovie.Id).FirstOrDefault();
if (movie == null) {
movie = rawMovie;
_store.Movies.Insert(movie);
movie.Cast.Where(n => _store.Cast.Get(e => e.Id == n.Id).Select(e => e.Id).Contains(n.Id))
.ToList()
.ForEach(c => _store.Context.Entry(c).State = EntityState.Unchanged);
}
var user = await UserManager.FindByIdAsync(User.Identity.GetUserId());
if(user == null) return Request.CreateErrorResponse(HttpStatusCode.Unauthorized, "Invalid User");
user.Movies.Add(movie);
return Request.CreateResponse(_store.SaveChanges());
} catch (Exception e) {
return Request.CreateErrorResponse(HttpStatusCode.InternalServerError, e.Message);
}
}
I use the new IdentityDbContext as my single context, so it's used for both authentication and my POCO models - meaning that both Movie and User : IdentityUser share the same the context.
public class ApplicationContext : IdentityDbContext<User> {
public DbSet<Movie> Movies { get; set; }
public DbSet<Character> Cast { get; set; }
public ApplicationContext()
: base("MoovyConnection", throwIfV1Schema: false) { }
public static ApplicationContext Create() {
return new ApplicationContext();
}
protected override void OnModelCreating(DbModelBuilder modelBuilder) {
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<Movie>().HasMany(m => m.Cast).WithMany(c => c.Movies)
.Map(t => t.MapLeftKey("Movid_Id").MapRightKey("Character_Id").ToTable("MovieCharacters"));
}
}
I've found this example but user.Movies does not have an attach method as it is only an ICollection.
What is the proper way to associate two objects to each other in a many-to-many relationship in EF6?

EntityValidationException is thrown when saving my entity

I got this message when debugged in catch (Exception e). When User fill in all the information, Address and Payment View will get the SalesOrderID and redirect to Complete View. But it didn't show the Complete when it done.
[HttpPost]
public ActionResult AddressAndPayment(SalesOrderHeader order,Customer customer, Address address ,FormCollection values)
{
ViewBag.PersonType = new SelectList(new[] { "EM", "SC", "VC", "IN" } // work
.Select(x => new { value = x, text = x }),
"value", "text");
try
{
if (string.Equals(values["PromoCode"], PromoCode, StringComparison.OrdinalIgnoreCase) == false)
{
return View(order);
}
else
{
order.AccountNumber = User.Identity.Name;
order.OrderDate = DateTime.Now;
address.ModifiedDate = DateTime.Now; // maybe this error
order.Address.PostalCode = "12345";
//Save Order
BikeDBs.SalesOrderHeaders.Add(order);
try
{
BikeDBs.SaveChanges();
}
catch (DbEntityValidationException e)
{
foreach (var entityValidationErrors in e.EntityValidationErrors)
{
foreach (var validationError in entityValidationErrors.ValidationErrors)
{
Console.WriteLine("Properties: {0} Error: {1}", validationError.PropertyName, validationError.ErrorMessage);
}
}
}
//Process Order
var cart = ShoppingCart.GetCart(this.HttpContext);
cart.CreateOrder(order);
//cart.CreateOrder(order1);
return RedirectToAction("Complete", new { id = order.SalesOrderID });
}
}
catch (Exception exception)
{
//Invalid - redisplay with errors
return View(order);
}
All I want is when the Order is saved, it will redirect to Complete. But in this case, it's not. And here is Address model:
public partial class Address
{
public Address()
{
this.SalesOrderHeaders = new HashSet<SalesOrderHeader>();
this.SalesOrderHeaders1 = new HashSet<SalesOrderHeader>();
}
public int AddressID { get; set; }
public string AddressLine1 { get; set; }
public string AddressLine2 { get; set; }
public string City { get; set; }
public int StateProvinceID { get; set; }
public string PostalCode { get; set; }
public System.Guid rowguid { get; set; }
[Required()]
public Nullable<System.DateTime> ModifiedDate { get; set; }
public virtual StateProvince StateProvince { get; set; }
public virtual ICollection<SalesOrderHeader> SalesOrderHeaders { get; set; }
public virtual ICollection<SalesOrderHeader> SalesOrderHeaders1 { get; set; }
}
What's a solution and how to fix it?
You can do this pretty easily by using the ModelState, it should catch it. If it doesn't I added code into your catch block to catch it and display the page again with the errors using ModelState.AddModelError.
[HttpPost]
public ActionResult AddressAndPayment(SalesOrderHeader order,Customer customer, Address address ,FormCollection values)
{
ViewBag.PersonType = new SelectList(new[] { "EM", "SC", "VC", "IN" } // work
.Select(x => new { value = x, text = x }),
"value", "text");
if(ModelState.IsValid)
{
try
{
if (string.Equals(values["PromoCode"], PromoCode, StringComparison.OrdinalIgnoreCase) == false)
{
return View(order);
}
else
{
order.AccountNumber = User.Identity.Name;
order.OrderDate = DateTime.Now;
order.Address.PostalCode = values["PostalCode"];
//Save Order
BikeDBs.SalesOrderHeaders.Add(order);
try
{
BikeDBs.SaveChanges();
}
catch (DbEntityValidationException e)
{
foreach (var entityValidationErrors in e.EntityValidationErrors)
{
foreach (var validationError in entityValidationErrors.ValidationErrors)
{
// If this far add errors to model errors and show view again.
ModelState.AddModelError(validationError.PropertyName, validationError.ErrorMessage);
Console.WriteLine("Properties: {0} Error: {1}", validationError.PropertyName, validationError.ErrorMessage);
}
}
return View(order);
}
//Process Order
var cart = ShoppingCart.GetCart(this.HttpContext);
cart.CreateOrder(order);
//cart.CreateOrder(order1);
return RedirectToAction("Complete", new { id = order.SalesOrderID });
}
}
catch (Exception exception)
{
//Invalid - redisplay with errors
return View(order);
}
}
return View(order);
}
For my answer I assume that the properties PostalCode and PersonType are of type string and are defined as not nullable.
I think the error messages you get clearly say what the problem is. The properties PostalCode and PersonType are required, that means they need to have a value other than null.
So when you do not set the properties to a value other than null and you try to save your entity you will get the error messages.
To fix it you will net to set the properties to some values (maybe a default value) or you have to change your EntityModel to specify that these properties are nullable

What should I return from my service layer? DotNetOpenAuth

I am wondering what should I return in this case. My Ui and service layer are in different projects.
This is what happens
-> User Comes to Site -> User chooses openId provider and hits login -> Post back to controller
[HttpPost]
public ActionResult Login(LoginViewModel loginViewModel)
{
var test = accountService.SendOpenIdRequest(loginViewModel.OpenId);
}
public class LoginViewModel
{
public OpenId OpenId { get; set; }
}
So I take in a ViewModel that contains my domain class.
public class OpenId
{
public string Url { get; set; }
}
So far in my SendOpenIdRequest
public ? SendOpenIdRequest(OpenId openId)
{
var openIdRelyingParty = new OpenIdRelyingParty();
var response = openIdRelyingParty.GetResponse();
Identifier id;
if (Identifier.TryParse(openId.Url, out id))
{
try
{
var req = openIdRelyingParty.CreateRequest(openId.Url);
return req.RedirectingResponse
}
catch (ProtocolException ex)
{
}
}
return null;
}
Now this is where I get lost since their are so many things I could return.
I could return
return req.RedirectingResponse.AsActionResult()
However I think this would be bad as now I am depending on asp.net mvc ActionResult and if I say use this service layer for some other project(maybe I have a webservice that connects to a mobile application). It won't won't work to well.
I could return OutgoingWebResponse but I am not really sure what to do with it once I get it back.
I could also return the IAuthenticationRequest what is generated from CreateRequest()
Finally I could return my Domain Object(OpenId) with one of the ones I listed above in it.
You could return an OutgoingWebResponse:
public OutgoingWebResponse SendOpenIdRequest(OpenId openId)
{
using (var openIdRelyingParty = new OpenIdRelyingParty())
{
var response = openIdRelyingParty.GetResponse();
Identifier id;
if (Identifier.TryParse(openId.Url, out id))
{
try
{
var req = openIdRelyingParty.CreateRequest(openId.Url);
return req.RedirectingResponse
}
catch (ProtocolException ex)
{
}
}
return null;
}
}
and then in your controller:
[HttpPost]
public ActionResult Login(LoginViewModel loginViewModel)
{
var response = accountService.SendOpenIdRequest(loginViewModel.OpenId);
if (response == null)
{
ModelState.AddModelError(
"openid_identifier",
"The specified login identifier is invalid"
);
return View();
}
return response.AsActionResult();
}