I have a model that looks as follows:
public partial class RoutePartner
{
public Guid Id { get; set; }
[Required]
public Guid PartnerId { get; set; }
[Required]
public string RouteCompany { get; set; }
}
The partnerId comes from another TABLE that has an Id and Name. I'm trying to build my CRUD forms to load the list of partners and make them available to the form. That is the best way to do this in MVC Core? I see many .NET examples but none for CORE.
One last point is that partner drop down should be populated by a stored procedure.
Thanks
Dan
Your class doesn't have to be the same as your database. Why not have a class called Partner that has the ParnterID and PartnerName? In your RoutePartner class have a Partner property, instead of just the ID.
Public class SomeViewModel //Used by your view
{
public RouteParnter {get; set;}
public ParnterList List<SelectListItem> {get; set;}
public static List<SelectListItem> PopulateDropdown()
{
List<SelectListItem> ls = new List<SelectListItem>();
DataTable someTable = Database.LoadParnters(); // Database is your data layer
foreach (var row in someTable.Rows)
{
ls.Add(new SelectListItem() { Text = row["PartnerName"].ToString();, Value = row["PartnerID"].ToString(); });
}
return ls;
}
}
public partial class RoutePartner
{
public Guid Id { get; set; }
[Required]
public Partner SomePartner { get; set; }
[Required]
public string RouteCompany { get; set; }
public RouteParnter(Guid id)
{
DataRow returnedRow = Database.LoadRouteParnter(id);
Id = id;
// Assuming your DataRow returned has all the info you need.
RouteCompany = row["RouteCompany"];
SomePartner = new Partner();
SomePartner.PartnerId = row["PartnerID"]; // cast to guid, just example
SomePartner.PartnerName = row["PartnerName"].ToString();
}
}
public class Partner
{
public Guid PartnerId { get; set; }
public string PartnerName { get; set; }
}
I would loosely couple the data layer. You could use ADO.NET or any other way to communicate with the database. I usually do this by creating a parameterized constructor (say taking in an ID to load), to call the data layer. The data layer returns a DataTable, DataRow, DataSet, or a custom object. In the constructor, you populate the values of the RouteParnter object. Likewise you could have a List in the ViewModel.
I wanted to provide a quick update to how I solved this issue. I ended up creating a PartnerContext for EF. Once created, in controllers that need access to the list, I placed PartnerContext.ToList() into ViewBag and then used ASP helpers to load the list.
Related
I am learning Asp.net MVC.
I am building an application in which I have following components:
1.Student class:
public class Students
{
[Required(ErrorMessage="Student Id is required")]
[Display(Name="Student Id")]
public int Sid { get; set; }
[Required]
public string FirstName { get; set; }
[Required]
public string LastName { get; set; }
[Required]
public string Address { get; set; }
[DisplayFormat(ApplyFormatInEditMode = true, DataFormatString = "{0:yyyy-MM-dd}")]
[DataType(DataType.Date)]
public DateTime DOB { get; set; }
[Required]
public string Email { get; set; }
}
2.StudentManager class
public List<Students> sList = new List<Students>()
{
new Students{Sid=1001,FirstName="A",LastName="T",DOB=Convert.ToDateTime("14-10-1987"),Email="a#b.com",Address="I"},
new Students{Sid=1002,FirstName="B",LastName="U",DOB=Convert.ToDateTime("14-10-1987"),Email="a#b.com",Address="I"},
new Students{Sid=1003,FirstName="C",LastName="V",DOB=Convert.ToDateTime("14-10-1987"),Email="a#b.com",Address="I"},
new Students{Sid=1004,FirstName="D",LastName="W",DOB=Convert.ToDateTime("14-10-1987"),Email="a#b.com",Address="I"},
new Students{Sid=1005,FirstName="E",LastName="X",DOB=Convert.ToDateTime("14-10-1987"),Email="a#b.com",Address="I"},
new Students{Sid=1006,FirstName="F",LastName="Y",DOB=Convert.ToDateTime("14-10-1987"),Email="a#b.com",Address="I"},
new Students{Sid=1007,FirstName="G",LastName="Z",DOB=Convert.ToDateTime("14-10-1987"),Email="a#b.com",Address="I"},
};
public void Edit(Students s)
{
Students stud = sList.Where(st => st.Sid == s.Sid).First();
stud.FirstName = s.FirstName;
stud.LastName = s.LastName;
stud.Email = s.Email;
stud.DOB = s.DOB;
}
3. And a student controller
public static StudentManager sm = new StudentManager();
// GET: Student
public ActionResult Index()
{
return View(sm.sList);
}
public ActionResult Edit(int? id)
{
Students student = sm.sList.Where(s => s.Sid == id).First();
return View(student);
}
[HttpPost]
public ActionResult Edit(Students s)
{
sm.Edit(s);
return RedirectToAction("Index");
}
My queries are:
1.I am able to edit a student's details.But if I do not use the keyword static in Controller then it is not updating.
2.How Model Binding automatically passes Student object to HttpPost Edit()?
Can someone please explain?
Every time you create StudentManager class you are creating new collection of students.
When StudentManager is static it is created only once, so you ll use the same object for every call and modify collection in it.
When StudentManager is not static for every call you ll create new object with new collection, so your edit will modify collection within object that ll be thrown away with a next call (like view).
It's not a good practice to have references to dependencies as static properties. I would recommend to make you collection property static and initialize it only once for life cycle of application and later migrate to use db or file or a call, depending of what your goal is.
Model Binding uses one of the registered Model Binders, some are provided by default so for main types of data, like JSON, you don't have to to anything.
I would definitely recommend to read a good book about mvc, to get the mechanics behind it. As the good and the bad part about it is it works like "magic" =)
Example:
I have table Orders and table OrderPositions.
public partial class Orders
{
public Orders()
{
this.OrderPositions = new HashSet<OrderPositions>();
}
public int OrderId { get; set; }
public string Title { get; set; }
public virtual ICollection<OrderPositions> OrderPositions { get; set; }
}
public partial class OrderPositions
{
public int OrderPositionId { get; set; }
public int OrderId { get; set; }
public string Name { get; set; }
public virtual Orders Orders { get; set; }
}
On the view user can modify single record from OrderPositions table.
In controller:
[HttpPost]
public ActionResult Edit(OrderPositions orderPosition)
{
// save orderPosition
}
So parameter orderPosition.Orders should be = null because on the form in view user can modify only order position. But can user hack it? I mean that in parameter orderPosition.Orders won't be null and I update record not only in table OrderPositions but also in table Orders? Or ASP.NET MVC prevent from that situation?
It really depends on what you do here
[HttpPost]
public ActionResult Edit(OrderPositions orderPosition)
{
// save orderPosition
}
If you're saving the whole entity then yes there is nothing stopping a user passing over addition entity properties. There are a few ways to prevent this though, here are a couple...
1.Create a new entity at the point of saving
[HttpPost]
public ActionResult Edit(OrderPositions orderPosition)
{
if(ModelState.IsValid)
{
var order = new OrderPositions
{
OrderPositionId = orderPosition.OrderPositionId,
OrderId = orderPosition.OrderId,
Name = orderPosition.Name
};
//Then save this new entity
}
}
2.Create a Model specific to the entity's action
public class EditOrderPosition
{
[Required]
public int PositionId { get; set; }
[Required]
public int Id { get; set; }
[Required]
public string Name { get; set; }
}
[HttpPost]
public ActionResult Edit(EditOrderPosition model)
{
if(ModelState.IsValid)
{
var order = new OrderPositions
{
OrderPositionId = model.PositionId,
OrderId = model.Id,
Name = model.Name
};
//Then save this new entity
}
}
I generally go with the 2nd method as it stops direct user involvement with my entities. As a rule of thumb I never use entity objects as parameters in controller actions.
Hope this helps
Yes they can. This is one reason I do not expose my entities as a parameter to action methods, instead I use DTOs that only have the properties that I expect.
This is an example of the Mass Assignment Vulnerability.
Yes, there is nothing preventing a rogue app calling your endpoint with arbitrary data. Always validate everything serverside.
I use Asp.Net MVC, Entity Framework. I have a form it looks like below.
Here, dropdownlist is filled from a table(types). Checkboxes is filled from another table(test). Tables are like below:
public class Types
{
public int TypesID{get;set;}
public string TestName { get; set; }
public string TestExplanation { get; set; }
public int TestTime { get; set; }
}
public class Tests
{
public int TestID{get;set;
public string Name { get; set; }
public string Code { get; set; }
}
public class Types_Tests
{
public int Types_TestsID{ get; set; }
public int TypesID { get; set; }
public int TestsID { get; set; }
public virtual Types Types { get; set; }
public virtual Tests Tests { get; set; }
}
Types_test table is relation table between Types and Tests. When I click Kaydet button, it shuld save type and checked tests. I made this operation using ViewBag, javascript and hdnvalue.I added checked checkboz values to a hdntext. I made saving process like below:
[HttpPost]
public ActionResult Index(string drpType, string hdntesttypes)
{
var TypeList = Types.GetAll();
ViewBag.TypesList = new SelectList(TypeList, "Id", "Name");
var testypeList = testTypes.GetAll();
ViewBag.TestTypesList = new SelectList(testypeList, "Id", "TestName");
GenericRepository<TestDisabledTypes> testDisabledRepository = new GenericRepository<TestDisabledTypes>(_context);
if (!string.IsNullOrEmpty(hdntesttypes))
{
string[] disabletypesArray = hdntesttypes.Split(',');
using (TransactionScope trns = new TransactionScope())
{
for (int i = 0; i < disabletypesArray.Length; i++)
{
Test_Types types = new Test_Types ();
types.TestTypesID = Convert.ToInt32(disabletypesArray[i]);
types.TypesID = Convert.ToInt32(drpType);
testDisabledRepository.Insert(types);
}
trns.Complete();
}
}
return View();
}
It wokrs. But I search better solution for this process. Can someone give me any idea?
Thanks.
If you don't need additional attributes for your entity class, you don't need create link table.
Just define the following class, and EF will generate the link table for you automatically.
public class Type
{
public int TypesID{get;set;}
public string TestName { get; set; }
public string TestExplanation { get; set; }
public int TestTime { get; set; }
public ICollection<Test> Tests { get; set; }
}
public class Test
{
public int TestID{get;set;
public string Name { get; set; }
public string Code { get; set; }
public ICollection<Type> Types {get;set;}
}
Well, in EntityFramework if you want to create a many to many relation object you need to create new object of "linking" entity. Unfortunately, it is not possible to add first object, add second object and say "Guys, you are in many to many relationships. Are you happy then?" :) You need to create relation object, set appropriate fields in it (I think these are ids of two objects itself) and add it to relation collection (entity) in your model. But before doing so you need to be sure that objects with data you are linking with are already exists in database. Otherwise you'll get an error
Also it's not necessary to create manually transaction because EF does it for you automatically each time you get/save your data
Hope someone can help - this has been bugging me for around 2 hours - its probably something simple :)
Kendo UI Grid sends a request to my controller
http://localhost:1418/user/update?UserID=1&UserName=Admin&RoleName=Admin&Email=c.j.hannon%40gmail.com&Active=true&Company%5BCompanyID%5D=1&Company%5BCompanyName%5D=asd
However, the controller class 'Company' isnt bound by the binder? Can any one help my view model and controller action signature are below:
[HttpGet]
public JsonResult Update(UserViewModel model)
{
svcUser.UpdateUser(new UpdateUserRequest() {
UserID=model.UserID,
RoleID = model.RoleName,
Email = model.Email,
Active = model.Active.GetValueOrDefault(false),
UserName = model.UserName
});
return Json("", JsonRequestBehavior.AllowGet);
}
public class UserViewModel
{
public int UserID { get; set; }
public string UserName { get; set; }
public string RoleName { get; set; }
public string Email { get; set; }
public bool? Active { get; set; }
public CompanyViewModel Company { get; set; }
}
Cheers
Craig
A few things. Your immediate problem is that Company is mapped to a complex object not a primitive type. Kendo Grid just does not do this (as of this writing). Just guessing, but you probably want to setup a foreign key binding on the Grid and just pass back the Id of the company from a listbox. This is not as bad as you think and it will immediatly fix your problem and look nice too.
Maybe personal taste but seems to be a convention. Use the suffix ViewModel for the model that is bound to your View and just the suffix Model for your business objects. So a Kendo Grid is always populated with a Model.
Ex.:
public class UserModel
{
public int UserID { get; set; }
public string UserName { get; set; }
public string RoleName { get; set; }
public string Email { get; set; }
public bool? Active { get; set; }
public int CompanyID { get; set; }
}
public class CompanyModel
{
public int ID { get; set; }
public string Name { get; set; }
}
public class UserViewModel
{
public UserModel UserModel { get; set; }
public IList<CompanyModel> Companies { get; set; }
}
public ActionResult UserEdit(string id)
{
var model = new UserViewModel();
model.UserModel = load...
model.Companies = load list...
return View(model);
}
#model UserViewModel
...
column.ForeignKey(fk => fk.CompanyId, Model.Companies, "ID", "Name")
(Razor Notation)
BUT! This is just an example, you are better off Ajax loading the Grid with the IList becuase I assume you have many Users in the Grid at once, though you could server bind off the ViewModel with a List too. But the list of Companies is probably the same every time, so map it to the View just liek this rather than Ajax load it every time you do a row edit. (not always true)
I have a DbDataController which delivers a List of Equipment.
public IQueryable<BettrFit.Models.Equipment> GetEquipment() {
var q= DbContext.EquipmentSet.OrderBy(e => e.Name);
return q;
}
In my scaffolded view everything looks ok.
But the Equipment contains a HashSet member of EquipmentType. I want to show this type in my view and also be able to add data to the EquipmentType collection of Equipment (via a multiselect list).
But if I try to include the "EquipmentType" in my linq query it fails during serialisation.
public IQueryable<BettrFit.Models.Equipment> GetEquipment() {
var q= DbContext.EquipmentSet.Include("EquipmentType").OrderBy(e => e.Name);
return q;
}
"Object Graph for Type EquipmentType Contains Cycles and Cannot be Serialized if Reference Tracking is Disabled"
How can I switch on the "backtracking of references"?
Maybe the problem is that the EquipmentType is back-linking through a HashSet? But I do not .include("EquipmentType.Equipment") in my query. So that should be ok.
How is Upshot generating the model? I only find the EquipmentViewModel.js file but this does not contain any model members.
Here are my model classes:
public class Equipment
{
public Equipment()
{
this.Exercise = new HashSet<Exercise>();
this.EquipmentType = new HashSet<EquipmentType>();
this.UserDetails = new HashSet<UserDetails>();
}
public int ID { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public string Picture { get; set; }
public string Link { get; set; }
public string Producer { get; set; }
public string Video { get; set; }
public virtual ICollection<EquipmentType> EquipmentType { get; set; }
public virtual ICollection<UserDetails> UserDetails { get; set; }
}
public class EquipmentType
{
public EquipmentType()
{
this.Equipment = new HashSet<Equipment>();
this.UserDetails = new HashSet<UserDetails>();
}
public int ID { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public virtual ICollection<Equipment> Equipment { get; set; }
public virtual ICollection<UserDetails> UserDetails { get; set; }
}
try decorating one of the navigation properties with [IgnoreDataMember]
[IgnoreDataMember]
public virtual ICollection<Equipment> Equipment { get; set; }
The model generated by upshot can be found on the page itself. In your Index view you will see the UpshotContext HTML helper being used (given that you are using the latest SPA version), in which the dataSource and model type are specified.
When the page is then rendered in the browser, this helper code is replaced with the actual model definition. To see that, view the source code of your page in the browser and search for a <script> tag that starts with upshot.dataSources = upshot.dataSources || {};
Check here for more info about how upshot generates the client side model.
As for the "backtracking of references", I don't know :)
I figured out - partially how to solve the circular reference problem.
I just iterated over my queried collection (with Include() ) and set the backreferences to the parent to NULL. That worked for the serialisation issue which otherwise already breaks on the server.
The only problem now is the update of a data entity - its failing because the arrays of the referenced entitycollection are static...
To solve the cyclic backreference, you can use the IgnoreDataMember attribute. Or you can set the back reference to NULL before returning the data from the DbDataController
I posted a working solution to your problem in a different question, but using Entity Framework Code First.
https://stackoverflow.com/a/10010695/1226140
Here I show how to generate your client-side model manually, allowing to you to map the data however you please