I started learning C# and I want to update my model using the [HttpPost] annotation. I tried removing the [FromBody]Item itm parameter and the fields on the repository but it's not working either. Below is my code.
Controller:
[HttpPost("{id}")]
public ActionResult<Item> UpdateItem([FromBody]Item itm, int id)
{
var getItem = _repository.GetItemById(id);
if (getItem == null)
{
return NotFound();
}
_repository.UpdateItem(itm);
_repository.SaveChanges();
return Ok(getItem);
}
Repository:
public void UpdateItem(Item itm)
{
if (itm == null)
{
throw new ArgumentNullException(nameof(itm));
}
var itemToUpdate = this.GetItemById(itm.Id);
if (itm.Name != null)
{
itemToUpdate.Name = itm.Name;
}
itemToUpdate.Price = itm.Price;
itemToUpdate.Condition = itm.Condition;
itemToUpdate.Size = itm.Size;
itemToUpdate.DateSold = itm.DateSold;
itemToUpdate.SellMethod = itm.SellMethod;
_context.Items.Update(itemToUpdate);
}
Interface:
void UpdateItem(Item itm);
Model:
public int Id { get; set; }
[Required]
public string Name { get; set; }
public int Price { get; set; }
public string Condition { get; set; }
public string Size { get; set; }
public string DateSold { get; set; }
public string SellMethod { get; set; }
First of all verify that you're sending that item correctly:
Is the form correct and pointing to that method of your controller?
Are you sending that item via the form (have you used the provided methods for this) ?
After that, if you're sending the item in the body of your post request, then verify the item in the method's parameter is available.
EDIT:
Well, as already discussed with Panagiotis you should rather directly use the DbContext itself as it already provides everything you need.
[HttpPost("{id}")]
public ActionResult<Item> UpdateItem(int id, [FromBody]Item itemData)
{
var foundItem = _dbContext.Items.SingleOrDefault(x => x.Id == id);
if (foundItem == null)
{
return NotFound();
}
foundItem.Name = itemData.Name;
foundItem.Size = itemData.Size;
// and so on
_dbContext.SaveChanges();
return Ok(foundItem);
}
Another way to keep your current structure, but it's not recommended, would be the following:
[HttpPost("{id}")]
public ActionResult<Item> UpdateItem(int id, [FromBody]Item itemData)
{
var updatedItem = _repository.UpdateItem(id, itemData);
if (updatedItem == null)
{
return NotFound();
}
return Ok(updatedItem);
}
public void UpdateItem(int id, Item itemData)
{
// you can validate parameters and throw errors if e.g. itemData == null
var originalItem = GetItemById(id); // this should internally get the item e.g. _dbContext.Items.Where(x => x.id == itemData.Id);
if(originalItem == null)
{
return null;
}
originalItem.Name = itemData.Name;
originalItem.Price = itemData.Price;
originalItem.Condition = itemData.Condition;
originalItem.Size = itemData.Size;
originalItem.DateSold = itemData.DateSold;
originalItem.SellMethod = itemData.SellMethod;
SaveChanges(); // guess this will be _dbContext.SaveChanges() instead
return originalItem;
}
Well, you could also change it to first load the item and then pass the originalItem and the itemData into the UpdateItem method inside your repository. But as you see the better way to directly use the DbContext is more clearer and shorter.
Related
I have a method in my Asp.Net Core Api for insert new entity which get a ViewModel as an argument and returns another ViewModel as result.
I have added Set() method as one of its properties to return list of CreditUserViewModel and it's working fine.
public async Task<IEnumerable<CreditUserViewModel>> GetAll()
{
return await Task.Run(() =>
_context.Credit
.Select(CreditUserViewModel.Set));
}
But while I want to return single object, I have no access to Set() method. Why?
Result ViewModel :
public class CreditUserViewModel
{
public string PlanTitle { get; set; }
public string CardSerial { get; set; }
public DateTime PurchaseDate { get; set; }
public int ValidationDays { get; set; }
public int PaidAmount { get; set; }
public string PurchaseCode { get; set; }
public static CreditUserViewModel Set(Credit credit)
{
return new CreditUserViewModel
{
PlanTitle = credit.CreditPlan.Title,
CardSerial = credit.Card.SerialNumber,
PurchaseDate = DateTime.UtcNow,
ValidationDays = credit.CreditPlan.ValidationDays,
PaidAmount = credit.CreditPlan.Cost,
PurchaseCode = credit.PurchaseCode
};
}
}
Add() method which I expect to return single CreditUserViewModel:
public async Task<CreditUserViewModel> Add(CreditRegisterViewModel credit)
{
var newCredit = new Credit
{
CardId = credit.CardId,
CreditPlanId = credit.CreditPlanId,
PurchaseCode = credit.PurchaseCode,
RechargeDate = DateTime.UtcNow
};
_context.Entry(newCredit).State = EntityState.Added;
try
{
await _context.SaveChangesAsync();
}
catch
{
throw new Exception();
}
return new CreditUserViewModel.Set(newCredit); **// Error is in this line: The type name Set does not exist in the type CreditUserViewModel**
}
Error message : The type name Set does not exist in the type
CreditUserViewModel
Shouldn't that be something like:
public async Task<IEnumerable<CreditUserViewModel>> GetAll()
{
var data = _context.Credit
.Select( c => CreditUserViewModel.Set(c) )
.ToList();
return Task.FromResult(data);
}
And this
return new CreditUserViewModel.Set(newCredit);
should be
return CreditUserViewModel.Set(newCredit);
Since CreditUserViewModel.Set is a method, not a type, and by convention would normally be called Create as it's in lieu of a constructor. Or implemented as an Extension method called ToViewModel() targeting the entity type, something like:
public static CreditUserViewModel ToViewModel(this CreditUser entity)
{
...
}
Role Entity Of My Project has Multiple Action With This Modeling:
{
public string Uid { get; set; }
public string Descript { get; set; }
public bool Premitive { get; set; }
public virtual ICollection<ActionDto> Actions { get; set; }
public string Name { get; set; }
public bool IsDeleted { get; set; }
}
I use UnitOfWork Repository Pattern
The create method is:
public async Task<IHttpActionResult> Create([FromBody] RoleFullDto dto)
{
try
{
if (dto.Actions == null || dto.Actions.Count <= 0)
return BadRequest();
//If I Pass Only action uid, return EntityModelException
//When I Pass Complete Entity, Create New Action
//foreach (var action in dto.Actions)
//{
//var act = UnitOfWork.ActionRepository.Get(action.Uid);
//action.ActionName = act.ActionName;
//action.MenuId = act.MenuId;
//action.PersianActionName = act.PersianActionName;
//}
var role = ModelFactory.GetRole(dto);
if (role == null)
return Content(HttpStatusCode.InternalServerError, dto);
var result = await this.AppRoleManager.CreateAsync(role);
if (!result.Succeeded)
{
return GetErrorResult(result);
}
return ActionResult<Role>(role, null, "CreateRole", HttpStatusCode.Created);
}
catch (Exception e)
{
Console.WriteLine(e);
return InternalServerError();
}
}
I pass this parameters To create new role:
{
Name: "TestRole1",
Descript: "This is Test",
Premitive: false,
Actions: [{uid:1},{uid:2},{uid:3}]
}
but this method wand to add 3 new Action that Exist in Databese
How Can I Create Role With Exist Actions?
Since you are passing entities that doesn't come from the db, Entity Framework tries to create them. You could use the Attach method of DBSet to link you action objects to entities. But since you're not using a DBSet directly you could also do something like this :
dto.Actions = dto.Actions.Select(a => UnitOfWork.ActionRepository.Get(a.Uid)).ToList();
I'm using OData with Entity Framework. Let's assume that I have following models and controller method:
public class Model1
{
public int Id { get; set; }
public int Field2 { get; set; }
public int FieldFromOtherService { get; set; }
public Model2 Model2 { get; set; } // Navigation Property
public int Model2Id { get; set; }
}
public class Model2
{
public int Id { get; set; }
public int Field { get; set; }
}
[HttpGet, EnableQuery]
public IQueryable<Model1> Get()
{
return modelRepository.List();
}
Model1 has property FieldFromOtherService that is not taken from DB - it is retrieved from other service. I need a way to fill this property after applying OData top, skip, expand and select clause.
Is there a way to accomplish that? I've tried to make a wrapper to IQueryable and call action after evaluation but it crash when query is more complicated.
Finally, I manage to accomplish my goals with #zaitsman suggestion. It was harder then I thought because OData adds wrappers that are not accessible (classes SelectAllAndExpand, SelectAll, SelectSomeAndInheritance, SelectSome). When expand is used, it is necessary to extract DTO from the wrapper. My code looks more or less like this:
[HttpGet]
public IHttpActionResult Get(ODataQueryOptions<Model1> options)
{
var result = modelRepository.List();
Action<ICollection<Model1>> postAction = collection => { Console.WriteLine("Post Action"); };
return ApplyOdataOptionsAndCallPostAction(result, options, postAction);
}
private IHttpActionResult ApplyOdataOptionsAndCallPostAction<T>(
IQueryable<T> baseQuery,
ODataQueryOptions<T> options,
Action<ICollection<T>> postAction)
where T : class
{
var queryable = options.ApplyTo(baseQuery);
var itemType = queryable.GetType().GetGenericArguments().First();
var evaluatedQuery = ToTypedList(queryable, itemType);
var dtos = ExtractAllDtoObjects<T>(evaluatedQuery).ToList();
postAction(dtos)
return Ok(evaluatedQuery, evaluatedQuery.GetType());
}
private static IList ToTypedList(IEnumerable self, Type innerType)
{
var methodInfo = typeof(Enumerable).GetMethod(nameof(Enumerable.ToList));
var genericMethod = methodInfo.MakeGenericMethod(innerType);
return genericMethod.Invoke(null, new object[]
{
self
}) as IList;
}
private IEnumerable<T> ExtractAllDtoObjects<T>(IEnumerable enumerable)
where T : class
{
foreach (var item in enumerable)
{
if (item is T typetItem)
{
yield return typetItem;
}
else
{
var result = TryExtractTFromWrapper<T>(item);
if (result != null)
{
yield return result;
}
}
}
}
private static T TryExtractTFromWrapper<T>(object item)
where T : class
{
if (item is ISelectExpandWrapper wrapper)
{
var property = item.GetType().GetProperty("Instance");
var instance = property.GetValue(item);
if (instance is T val)
{
return val;
}
}
return null;
}
private IHttpActionResult Ok(object content, Type type)
{
var resultType = typeof(OkNegotiatedContentResult<>).MakeGenericType(type);
return Activator.CreateInstance(resultType, content, this) as IHttpActionResult;
}
I'm confused, I have this method:
public IEnumerable<ProfessionalModel> GetAllProfessionals()
{
return Context.ProfessionalContext
.Include(x => x.UserAccountModel)
.Include(x => x.UserAddressModel).ToList();
}
If I call it using a console UI it return one record (it works), but If I call it using my MVC application it returns no one record!
Console UI:
ProfessionalContext pcontext = new ProfessionalContext();
var list = pcontext.GetAllProfessionals();
foreach (var i in list)
{
Console.WriteLine(i.Name);
}
Console.ReadKey();
MVC Web UI:
public class ProfessionalController : Controller
{
public ProfessionalContext professionalContext { get; set; }
public ActionResult Index()
{
professionalContext = new ProfessionalContext();
var professionals = professionalContext.GetAllProfessionals();
if (professionals == null)
{
return HttpNotFound();
}
return View(professionals);
}
}
And the problem is not at View, because I've debugged it and the var professionals have no records.
Any idea?
EDIT
My Context:
public class TCCDatabase : DbContext
{
public DbSet<UserAccountModel> UserAccountContext { get; set; }
public DbSet<ProfessionalModel> ProfessionalContext { get; set; }
public DbSet<UserAddressModel> UserAddressContext { get; set; }
}
Note that the return type is
IEnumerable<ProfessionalModel>
which means your debugger may sometimes not display the elements in the list till you ask it to evaluate.
Double check there are no records in the professionals variable as follows:
public ActionResult Index()
{
professionalContext = new ProfessionalContext();
var professionals = professionalContext.GetAllProfessionals();
Debug.Assert(professionals != null && professionals.Any());
if (professionals == null)
{
return HttpNotFound();
}
return View(professionals);
}
And if you want to see the results instantly in debugger etc. do this:
public List<ProfessionalModel> GetAllProfessionals() { //body }
I am standing on a complex issue for me. I need update some models, but I like to work with a generic class not to rewrite some code individually.
I need to update data that have lists on their properties, with possible exclusion or inclusion of items on these lists, but these lists can be of any other class / type. My questions are commented on the code.
These models are unrealistic and a bit absurds but have similarities with my real models, note that the logic is reversed on these relationships during updates.
Thanks for all.
public class RedNotebook
{
[Key]
public int Id { get; set; }
public string PageTitle { get; set; }
public virtual ICollection<Signature> Signatures { get; set; }
}
public class BlueNotebook
{
[Key]
public int Id { get; set; }
public DateTime Entrance { get; set; }
public DateTime Leave { get; set; }
public virtual ICollection<Guest> GuestList { get; set; }
}
public class Signature
{
[key]
public int Id { get; set; }
public string PeopleSignature { get; set; }
public int IdRedNotebook { get; set; }
public int IdBlueNotebook { get; set; }
[ForeignKey("IdRedNotebook")]
public virtual RedNotebook { get; set; }
[ForeignKey("IdBlueNotebook")]
public virtual BlueNotebook { get; set; }
}
public class Guest
{
[key]
public int Id { get; set; }
public string Name { get; set; }
public int SeatNumber { get; set; }
public int IdBlueNotebook { get; set; }
[ForeignKey("IdBlueNotebook")]
public virtual BlueNotebook { get; set; }
}
/**********************/
public void UpdateData(T newData, out string msg)
{
try
{
var propId = newData.GetType().GetProperty("Id");
if (propId == null)
{
msg = "Unable to identify the identity of the reported data.";
return;
}
int id = Convert.ToInt32(propId.GetValue(newData));
if (id <= 0)
{
msg = "Unable to identify the identity of the reported data.";
return;
}
//instance a determined DbContext and Model<T>
var contexto = new CtxCliente(DAO.Classes.Util.InstanciarConexao(strCripto, (DAO.Conectores) Conector));
var model = contexto.Set<T>();
var targetData = model.Find(id);
if (targetData == null)
{
model.Add(newData);
contexto.Entry(model).State = EntityState.Added;
msg = "An addition was made because there was no previous reference.";
}
if (Convert.ToInt32(targetData.GetType().GetProperty("Id").GetValue(targetData)) > 0)
{
contexto.Entry(targetData).CurrentValues.SetValues(newData);
contexto.Entry(targetData).State = EntityState.Modified;
msg = string.Empty;
}
//TODO - 1) GET THE VIRTUAL PROPERTIES OF WHICH TYPE targetData ICollection
//TODO - 2) COMPARE THE CONTENT OF VIRTUAL PROPERTIES OF targetData WITH THE CONTENTS OF VIRTUAL PROPERTIES UPDATE, BOTH ICollection
//TODO - 3) REMOVE EXCESS OF targetData AND / OR ADD THAT AS THE CASE MAY BE MISSING (A - CLEAR DIFFERENCE, B - ADD DIFFERENCE)
//through the properties to identify those that are of the collection type
foreach (var propertytargetData in targetData.GetType().GetProperties())
{
if (!propertytargetData.PropertyType.IsGenericType)
continue;
var propsNewData = newData.GetType().GetProperty(propertytargetData.Name);
#region
//if all list items were removed on update
if (propsNewData == null && propertytargetData != null)
{
// NOT TESTED, MAYBE NOT WORK CORRECTLY
propertytargetData.SetValue(targetData,null);
}
//If an item was included or removed
else if (propsNewData != null)
{
var valTargetData = propertytargetData.GetValue(targetData);
var valNewData = propsNewData.GetValue(newData);
var listItemsTargetData = (IEnumerable) valTargetData;
var listItemsNewData = (IEnumerable) valNewData;
int countItemsTargetData = listItemsTargetData.Cast<object>().Count();
int countItemsNewData = listItemsNewData.Cast<object>().Count();
if (countItemsTargetData > countItemsNewData) //remove discarded
{
foreach (var itemtargetData in listItemsTargetData)
{
var idItemtargetData = itemtargetData.GetType().GetProperty("Id").GetValue(itemtargetData);
var existing = (from object itemListNewData in listItemsNewData
select itemListNewData.GetType().GetProperty("Id").GetValue(itemListNewData))
.Any(iditemListNewData => (int) idItemtargetData == (int) iditemListNewData);
if (!existing) //remove
{
//how to remove from the list?????? (targetData)
}
else //update
{
foreach (var itemListNewData in listItemsNewData)
{
var props = itemListNewData.GetType().GetProperties();
foreach (var propertyInfo in props)
{
foreach (var item in listItemsTargetData)
{
var p = item.GetType().GetProperty(propertyInfo.Name);
if (p != null && !p.PropertyType.IsGenericType)
{
p.SetValue(item, propertyInfo.GetValue(itemListNewData));
}
}
}
}
}
}
}
else if (countItemsTargetData < countItemsNewData) //Items need to be included
{
foreach (var newItem in listItemsNewData)
{
var idnewItem = newItem.GetType().GetProperty("Id").GetValue(newItem);
if ((int) idnewItem == 0)
{
//how to insert in list???????? (targetData)
}
else // remove and/or update some before (reduntant!?)
{
foreach (var itemtargetData in listItemsTargetData)
{
var idItemtargetData = itemtargetData.GetType().GetProperty("Id").GetValue(itemtargetData);
var existing = (from object itemListNewData in listItemsNewData
select itemListNewData.GetType().GetProperty("Id").GetValue(itemListNewData))
.Any(iditemListNewData => (int)idItemtargetData == (int)iditemListNewData);
if (!existing) //remove
{
//how to remove from the list?????? (targetData)
}
else //update
{
foreach (var itemListNewData in listItemsNewData)
{
var props = itemListNewData.GetType().GetProperties();
foreach (var propertyInfo in props)
{
foreach (var item in listItemsTargetData)
{
var p = item.GetType().GetProperty(propertyInfo.Name);
if (p != null && !p.PropertyType.IsGenericType)
{
p.SetValue(item, propertyInfo.GetValue(itemListNewData));
}
}
}
}
}
}
}
}
}
}
}
contexto.SaveChanges(); //save data on model
}
catch(...){}
}
Haven't tested it . But it should work if both source and dest implement the same ICollection interface and T has an Id property of type System.Int32. It uses the new dynamic keyword that enables you to do duck typing ;
private class IdComparer : IEqualityComparer<object>
{
public bool Equals(object x, object y)
{
//return ((dynamic) x).Id = ((dynamic) y).Id; //previous with convertion error
return ((dynamic) x).Id == ((dynamic) y).Id;
}
public int GetHashCode(object obj)
{
return ((dynamic) obj).Id;
}
}
private static void Copy(IEnumerable source, IEnumerable dest)
{
var cmp = new IdComparer();
var toRemove = dest.Cast<object>().Except(source.Cast<object>(),cmp).ToList();
var toAdd= source.Cast<object>().Except(dest.Cast<object>(),cmp).ToList();
foreach(var item in toAdd)
{
// dynamic runtime tries to find method that matches signiture void Add(T value so we add dummy variable so that it knows to search for bool Add(T value)
var dummy= ((dynamic) dest).Add(item);
}
foreach (var item in toRemove)
{
var dummy= ((dynamic)dest).Remove(item);
}
}