Consider the following
MODEL
public partial class ElementType
{
public long ElementTypeId { get; set; }
public LocalizedString TypeName { get; set; }
}
[ComplexType]
public class LocalizedString
{
public string French { get; set; }
public string English { get; set; }
[NotMapped]
public string Current
{
get { return (string) LanguageProperty().GetValue(this,null); }
set { LanguageProperty().SetValue(this, value,null); }
}
public override string ToString()
{
return Current;
}
private PropertyInfo LanguageProperty()
{
string currentLanguage = Thread.CurrentThread.CurrentUICulture.DisplayName;
return GetType().GetProperty(currentLanguage);
}
}
CONTROLLER
public ActionResult ElementType_Read([DataSourceRequest]DataSourceRequest request)
{
List<ElementType> elementTypeList = db.ElementType.ToList();
IQueryable<ElementType> elementTypes = elementTypeList.AsQueryable();
DataSourceResult result = elementTypes.ToDataSourceResult(request, elementType => new
{
ElementTypeId = elementType.ElementTypeId,
TypeName = elementType.TypeName,
});
return Json(result);
}
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult ElementType_Destroy([DataSourceRequest]DataSourceRequest request, ElementType elementType)
{
if (ModelState.IsValid)
{
var currentElementType = db.ElementType.Find(elementType.ElementTypeId);
db.ElementType.Attach(currentElementType);
db.ElementType.Remove(currentElementType);
db.SaveChanges();
}
return Json(new[] { elementType }.ToDataSourceResult(request, ModelState));
}
Read action is working as expected. I'm getting the current TypeName in the Grid view by TypeName.Current
But if I try to execute any other actions, like Destroy or Update I'm getting this exception Can not convert an object of type 'Iolite.Models.LocalizedString' to type 'System.String'.
Any suggestion to fix it?
Regards
I suggest you to use DTOs (Data Transfer Object) to flat your model into easier to serialize/deserialize objects. You will also reduce the amount of data that will be transferred between your server & clients within every request.
For your particular implementation you might define a DTO as follow:
public class ElementTypeDTO
{
public long ElementTypeId { get; set; }
public string TypeName { get; set; }
}
According to this architectural pattern you have to change your actions as follow:
public ActionResult ElementType_Read([DataSourceRequest]DataSourceRequest request)
{
List<ElementType> elementTypeList = db.ElementType.ToList();
IQueryable<ElementType> elementTypes = elementTypeList.AsQueryable();
DataSourceResult result = elementTypes.ToDataSourceResult(request, elementType => new ElementTypeDTO
{
ElementTypeId = elementType.ElementTypeId,
TypeName = elementType.TypeName.Current,
});
return Json(result);
}
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult ElementType_Destroy([DataSourceRequest]DataSourceRequest request, ElementTypeDTO elementType)
{
if (ModelState.IsValid)
{
var currentElementType = db.ElementType.Find(elementType.ElementTypeId);
db.ElementType.Attach(currentElementType);
db.ElementType.Remove(currentElementType);
db.SaveChanges();
}
return Json(new[] { elementType }.ToDataSourceResult(request, ModelState));
}
Related
I want to pass to RedirectToAction model with List type property
For example, I have this simple model:
public class OrgToChooseFrom
{
public string OrgId { get; set; }
public string FullName { get; set; }
}
And complex model as this:
public class SelectCounteragentViewModel
{
public List<OrgToChooseFrom> Counteragents { get; set; }
public OrgToChooseFrom SelectedOrg { get; set; }
}
When I pass simple model with RedirectToAction every value is in place
[HttpGet]
public IActionResult ConfirmChoice(OrgToChooseFrom vm)
{
return View(vm);
}
But when I try to pass complex model SelectCounteragentViewModel, there are empty list and null for the "SelectedOrg" field
[HttpGet]
public IActionResult SelectFromCAOrganizations(SelectCounteragentViewModel vm)
{
return View(vm);
}
How can I do it?
RedirectToAction cannot pass complex model.You can try to use TempData as
Kiran Joshi said.Here is a demo:
public IActionResult Test()
{
SelectCounteragentViewModel vm = new SelectCounteragentViewModel { Counteragents = new List<OrgToChooseFrom> { new OrgToChooseFrom { OrgId ="1", FullName = "d" } }, SelectedOrg = new OrgToChooseFrom { OrgId = "1", FullName = "d" } };
TempData["vm"] = JsonConvert.SerializeObject(vm);
return RedirectToAction("SelectFromCAOrganizations", "ControllerName");
}
[HttpGet]
public IActionResult SelectFromCAOrganizations()
{
SelectCounteragentViewModel vm = JsonConvert.DeserializeObject<SelectCounteragentViewModel>(TempData["vm"].ToString());
return View(vm);
}
I am trying to do a simple POST request but it seems like [FromBody] cannot seem to understand more than 2 parameters.
The request:
[HttpPost]
public IEnumerable<EnergyMarket> addEnergy([FromBody] EnergyMarket energyMarket)
{
_energyMarketService.addEnergy(energyMarket.Name, energyMarket.StockIPO, energyMarket.EnergyPrice);
return _energyMarketService.Energies;
}
The object:
public class EnergyMarket
{
[JsonProperty("name")]
public string Name { get; set; }
[JsonProperty("stockipo")]
public string StockIPO { get; set; }
[JsonProperty("price")]
public double EnergyPrice { get; set; }
public EnergyMarket() { }
public EnergyMarket(string name, string stockIPO, double val)
{
this.Name = name;
this.StockIPO = stockIPO;
this.EnergyPrice = val;
}
}
Request (Content-type is application/json)
{
"name": "Air Canada",
"stockipo": "AC.TO",
"price": 13.12
}
When I go on POSTMAN and put the following request, it works fine for the first two parameters, but the price is always 0. When I changed the price from a double to string, the string was always null.
Default ASP.NET Core model binder does not use Newtonsoft.Json property attributes. Therefore, we should implement it ourselves.
Firstly, create implementation of IModelBinder that would read request body and deserialize it using Newtonsoft JsonConvert.DeserializeObject method
public class NewtonsoftModelBinder : IModelBinder
{
public async Task BindModelAsync(ModelBindingContext bindingContext)
{
string valueFromBody = string.Empty;
using (var sr = new StreamReader(bindingContext.HttpContext.Request.Body))
{
valueFromBody = await sr.ReadToEndAsync();
}
if (string.IsNullOrEmpty(valueFromBody))
{
return;
}
try
{
var model = JsonConvert.DeserializeObject(valueFromBody, bindingContext.ModelType);
bindingContext.Result = ModelBindingResult.Success(model);
}
catch
{
bindingContext.ModelState.TryAddModelError(
"", "Model can not be deserialized.");
}
return;
}
}
Then there are multiple ways how we can apply it to our code, but the easiest one is to put it as attribute on your EnergyMarket class
[ModelBinder(BinderType = typeof(NewtonsoftModelBinder))]
public class EnergyMarket
{
[JsonProperty("name")]
public string Name { get; set; }
[JsonProperty("stockipo")]
public string StockIPO { get; set; }
[JsonProperty("price")]
public double EnergyPrice { get; set; }
public EnergyMarket() { }
public EnergyMarket(string name, string stockIPO, double val)
{
this.Name = name;
this.StockIPO = stockIPO;
this.EnergyPrice = val;
}
}
Finally, we can see correct data in controller after Postman request
Environment:
I am working in Webapi. There is 2 entity classes which are follows;
public class Class1
{
public Class1()
{
this.items = new HashSet<Class2>();
}
public int Id { get; set; }
public string Name { get; set; }
public virtual ICollection<Class2> items { get; set; }
}
public class Class2
{
public int Id { get; set; }
public string Name { get; set; }
public int Class1Id { get; set; }
public virtual Class1 class1 { get; set; }
}
Business Layer:
The buniess layer have the following codes;
public class Class1Logic : IClass1Logic
{
private readonly IClass1Repository _repo;
public Class1Logic(IClass1Repository repository)
{
_repo = repository;
}
public async Task<bool> AddClass1ItemAsync(Class1 item)
{
_repo.Add(item);
bool status = await _repo.SaveAsync();
return status;
}
public async Task<Class1> GetClass1ItemAsync(int id)
{
return await _repo.GetAsync(id);
}
}
public class Class2Logic : IClass1Logic
{
private readonly IClass2Repository _repo;
public Class2Logic(IClass2Repository repository)
{
_repo = repository;
}
public async Task<bool> AddClass2ItemAsync(Class2 item)
{
_repo.Add(item);
bool status = await _repo.SaveAsync();
return status;
}
public async Task<Class2> GetClass2ItemAsync(int id)
{
return await _repo.GetAsync(id);
}
}
ViewModels:
public class Class1Model
{
public int Id { get; set; }
public string Name { get; set; }
}
public class Class2Model
{
public int Id { get; internal set; }
public string Name { get; set; }
public int Class1Id { get; set; }
public string Class1Name { get; internal set; }
}
Controllers:
There are 2 contrtollers like Class1Controller and Class2Controller. Both have all CRUD operations.
[RoutePrefix("api/class1items")]
public class Class1Controller : ApiController
{
private readonly IClass1Logic _class1Logic;
private ModelFactory TheFactory;
public Class1Controller(IClass1Logic class1Logic)
{
_class1Logic = class1Logic;
TheFactory = new ModelFactory();
}
[Route("")]
public async Task<IHttpActionResult> Post(Class1Model class1Model)
{
var item = TheFactory.Parse(class1Model);
bool result = await _class1Logic.AddClassItemAsync(item);
if (!result)
{
return BadRequest("Error");
}
string uri = Url.Link("GetLabById", new { id = item.Id });
return Created(uri, TheFactory.Create(item));
}
[Route("{id:int}", Name = "GetClass1ItemById")]
public async Task<IHttpActionResult> GetClass1Item(int id)
{
Class1 item = await _class1Logic.GetClassItemAsync(id);
if (item == null)
{
return NotFound();
}
return Ok(TheFactory.Create(item));
}
}
[RoutePrefix("api/class2items")]
public class Class2Controller : ApiController
{
private readonly IClass2Logic _class2Logic;
private ModelFactory TheFactory;
public Class2Controller(IClass2Logic class2Logic)
{
_class2Logic = class2Logic;
TheFactory = new ModelFactory();
}
[Route("")]
public async Task<IHttpActionResult> Post(Class2Model class2Model)
{
var item = TheFactory.Parse(class2Model);
***//Here item should include Class1 object even if user give ClassId in class2Model***
bool result = await _class2Logic.AddClassItemAsync(item);
if (!result)
{
return BadRequest("Error");
}
string uri = Url.Link("GetClass2ItemById", new { id = item.Id });
return Created(uri, TheFactory.Create(item));
}
}
There is not dependecies in Class1. So all operations are fine. In Class2Controller post method, I got the model object as following to create Class2.
{
"id": 0,
"name": "string",
"class1Id": 1
}
Understanding:
I need to return this viewmodel to user after the create the record. The record created successfully but when mapping to viewmodel i got null exception as Class1 object not in the Class2 object.
In order to get the Class2 object including class1 object, I need to give the class1Object in the request object.
For this i need to find the Class1 object with Class1Id in the request object.
ViewMapper Code:
public class ModelFactory
{
public Class1Model Create(Class1 item)
{
return new Class1Model
{
Id = item.Id,
Name = item.Name
};
}
public Class2Model Create(Class2 item)
{
return new Class2Model
{
Id = item.Id,
Name = item.Name,
Class1Id = item.class1.Id,
Class1Name = item.class1.Name
};
}
public Class1 Parse(Class1Model modelItem)
{
return new Class1
{
Id = modelItem.Id,
Name = modelItem.Name
};
}
public Class2 Parse(Class2Model modelItem)
{
return new Class2
{
Id = modelItem.Id,
Name = modelItem.Name,
Class1Id = modelItem.Class1Id,
***/*Issue Place*/
//class1 = Need to set property by getting object using modelItem.Class1Id***
};
}
}
Issue:
Now i need to call get method of Class1Controller by passing Class1Id.
How to call and is this correct? or my design is bad?
This is initial case. If my Class3 have both Class1 and Class2 again i need to call methods of Class1 and Class2.
Please help to find the correct solution in this case
Note: I added comments the issue area to understand
Well, just to fix this issue you need to manually call _class1Logic.GetClass1ItemAsync after saving. However this doesn't look good.
More elegant ways to fix it:
1) If you always need Class2.Class1 field to be filled use Include when you fetch data (in repository): dbContext.Set<Class2>().Include(c => c.class1).
2) Also you can turn on LazyLoading for EF - I assume it should work in your case.
3) Inject class1Repo to class2Logic and fix up class1 reference after saving - in case if you don't want to enable lazy loading or item was detached from context after save method
Thoughts about design:
I suggest you to look at Automapper or simular libraries instead of ModelFactory where you going to have all mapping logic
Edit: About generic repository: you can modify you GetAsync method
public async Task<T> GetAsync<T>(int id, params Expression<Func<T, object>>[] includes)
where T: class, IEntity
{
var query = context.Set<T>().AsQueryable();
if (includes.Length > 0)
{
query = includes.Aggregate(query,
(current, include) => current.Include(include));
}
return await query.FirstOrDefaultAsync(x => x.Id == id);
}
IEntity interface:
interface IEntity
{
int Id { get; }
}
With this implementation you can use
await _repo.GetAsync<Class2>(id, x => x.class1);
My models has some fields that are not to be presented in views (like Id field).
So, when I post the form, these fields return with "null" value, unless I insert then as hidden fields in form.
There are another away to update a model, using only the fields in form ?
My actual code:
[HttpPost]
[ValidateAntiForgeryToken]
public IActionResult Create(Profissao model)
{
if (ModelState.IsValid)
{
using (var escopo = Db.Database.BeginTransaction())
{
try
{
if (model.Id == 0)
Db.Profissoes.Add(model);
else
Db.Profissoes.Update(model);
Db.SaveChanges();
escopo.Commit();
return RedirectToAction("Index");
}
catch (Exception)
{
escopo.Rollback();
}
}
}
return View(model);
}
You should use Dto's (Data transfer objects) to handle this.
public class User
{
public string Name { get; set; }
public string Passord { get; set; }
public string Email { get; set; }
}
public class UserDto
{
public string Name { get; set; }
public string Passord { get; set; }
public string Email { get; set; }
public UserDto FromModel(User user)
{
Name = user.Name;
Passord = user.Passord;
Email = user.Email;
return this;
}
public User UpdataModel(User user)
{
user.Name = Name;
user.Email = Email;
return user;
}
}
then you can pass around the Dto object to your view and in your post.
your post controller should look somthing like
[HttpPost]
[ValidateAntiForgeryToken]
public IActionResult Create(ProfissaoDto model)
{
if (ModelState.IsValid)
{
using (var escopo = Db.Database.BeginTransaction())
{
try
{
if (model.Id == 0)
Db.Profissoes.Add(ProfissaoDto.UpdateModel(new Profissao()));
else
var model = Db.Profissao.find(Model.id);
Db.Profissoes.Update(ProfissaoDto.UpdateModel(model));
escopo.Commit();
return RedirectToAction("Index");
}
catch (Exception)
{
escopo.Rollback();
}
}
}
return View(model);
}
I would like to get into the habit of using ViewModels.
In the past I have only used them in my Create Actions and I never figured how to use them in Edit Actions. I used Domain Entities instead.
Let's say I have the following:
Using Entity Framework Code First
POCO class in Domain project
public class Person
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int PersonId { get; set; }
public string Name { get; set; }
public string Website { get; set; }
public DateTime? Created { get; set; }
public DateTime? Updated { get; set; }
}
In my Data Project
Abstract Folder:
public interface IPersonRepository
{
IQueryable<Person> People{ get; }
void SavePerson(Person person);
}
Concrete Folder:
EfDb class
public class EfDb : DbContext
{
public EfDb() : base("DefaultConnection") {}
public DbSet<Person> People{ get; set; }
}
EfPersonRepository class
#region Implementation of Person in IPersonRepository
public IQueryable<Person> People
{
get { return _context.People; }
}
public void SavePerson(Persona person)
{
if (person.PersonId == 0)
{
_context.People.Add(person);
}
else if (person.PersonId> 0)
{
var currentPerson = _context.People
.Single(a => a.PersonId== person.PersonId);
_context.Entry(currentPerson).CurrentValues.SetValues(person);
}
_context.SaveChanges();
}
#endregion
PersonCreateViewModel in WebUI Porject ViewModels folder
public class PersonCreateViewModel
{
[Required]
[Display(Name = "Name:")]
public string Name { get; set; }
[Display(Name = "Website:")]
public string Website { get; set; }
}
Person Controller and Create Action:
public class PersonController : Controller
{
private readonly IPersonRepository _dataSource;
public PersonController(IPersonRepository dataSource)
{
_dataSource = dataSource;
}
// GET: /Association/
public ActionResult Index()
{
return View(_dataSource.Associations);
}
// GET: /Person/Details/5
public ActionResult Details(int id)
{
return View();
}
// GET: /Person/Create
[HttpGet]
public ActionResult Create()
{
return View();
}
// POST: /Person/Create
[HttpPost]
public ActionResult Create(PersonCreateViewModel model)
{
if (ModelState.IsValid)
{
try
{
var Person = new Person
{
Name = Model.Name,
Website = model.Website,
Created = DateTime.UtcNow,
Updated = DateTime.UtcNow
};
_dataSource.SavePerson(person);
return RedirectToAction("Index", "Home");
}
catch
{
ModelState.AddModelError("", "Unable to save changes. ");
}
}
return View(model);
}
}
Now unless I am mistaken, I expect my PersonEditViewlModel to look exactly like my PersonCreateViewlModel. But I can't figure out how to use that in my Edit action, provided I also have to call SavePerson(Person person) like I did in my Create action.
Note: Please no suggestions of AutoMapper or ValueInjecter.
How is this done?
It'll be just like create except you need the record Id.
[HttpGet]
public ActionResult Edit(int id)
{
var personVm = _dataSource.People.Single(p => p.PersonId == id)
.Select(e => new PersonEditViewModel {
e.PersonId = p.PersonId,
e.Name = p.Name,
e.Website = p.Website
...
});
return View(personVm);
}
[HttpPost]
public ActionResult Edit(PersonEditViewModel model)
{
if (ModelState.IsValid)
{
var person = _dataSource.People.Single(p => p.PersonId == model.PersonId);
person.Name = model.Name;
person.Website = model.Website;
...
_dataSource.EditPerson(person);
return RedirectToAction("Index", "Home");
}
return View(model);
}
Edit:
So you don't do another query on edits
public void EditPerson(Person person)
{
_context.Entry(person).State = EntityState.Modified;
_context.SaveChanges();
}