In my model I have a one to many relationship between Users and Topics class. Code for the two classes is
public class Topic
{
public int TopicID { get; set; }
public int UserID { get; set; }
public virtual User User { get; set; }
[Display(Name="Topic Subject")]
public string TopicSubject { get; set; }
[Display(Name="Date")]
[DataType(DataType.Date)]
public DateTime TopicDate { get; set; }
[DisplayName("Name")]
public int CatagoryID { get; set; }
[DisplayName("FullName")]
public virtual Catagory Catagories { get; set; }
public virtual ICollection<Reply> Replies { get; set; }
}
and
public class User
{
public int UserID { get; set; }
[Display(Name = "First Name")]
[StringLength(50)]
public string FirstName { get; set; }
[Display(Name = "Last Name")]
[StringLength(50)]
public string LastName { get; set; }
[Display(Name="Password")]
[DataType(DataType.Password)]
public string UserPass { get; set; }
[DataType(DataType.EmailAddress)]
public string Email { get; set; }
[DataType(DataType.Date)]
public DateTime Date { get; set; }
[Display(Name = "Full Name")]
public string FullName
{
get { return FirstName + ", " + LastName; }
}
public virtual ICollection<Topic> Topics { get; set; }
public virtual ICollection<Reply> Replies { get; set; }
}
In my controller the action method I used an int id parameter to pass the id internally from the view
public ActionResult Create(int id)
{
ViewBag.CatagoryID = id;
ViewBag.UserID = id;
return View();
}
But when I try to access Topics/Create in my browser an error like this appears and it's bugging me:
Your Create method is the HttpGet version - meaning, why are you passing the id for an object that hasn't been created in order to "create" an object? This should be empty for a create, and the HttpPost should pass back the model so it can only then be created.
Try looking at the create mvc example: http://www.asp.net/mvc/overview/older-versions-1/getting-started-with-mvc/getting-started-with-mvc-part6
For the controller to work you need to pass the id in the query parameters. So you need to use the below url.
http://localhost:63952/Topics/Create?id=1
If you want the id to be optional you need to change your action method like below.
public ActionResult Create(int? id)
{
ViewBag.CatagoryID = id;
ViewBag.UserID = id;
return View();
}
The above will tell C# id param is optional and MVC will not expect it to be present on the query parameters
Hope this helps!!
Try localhost:63952/Create/5
5 refers to the id that you passed in the function Create
you can try any number it will works
Related
I start to learn .NET 5 to create full-stack applications. I create a model with the following code:
public class Contact
{
[Required]
[Display(Name = "First Name")]
public string FirstName { get; set; }
[Required]
[Display(Name = "Last Name")]
public string LastName { get; set; }
[Required]
public string Address1 { get; set; }
public string Address2 { get; set; }
public string City { get; set; }
public string State { get; set; }
[DataType(DataType.PostalCode)]
public int Zip { get; set; }
[Required]
[DataType(DataType.EmailAddress)]
public string Emanil { get; set; }
[DataType(DataType.PhoneNumber)]
public string Phone { get; set; }
// Seting up Date & Time
public DateTime Created { get; set; }
// Seting up using of image
[NotMapped]
[Display(Name = "Image")]
[DataType(DataType.Upload)]
public IFormFile ImageFile { get; set; }
public byte[] ImageData { get; set; }
public string ImageType { get; set; }
// Thinking about primary key that the database will manage
public int Id { get; set; }
// Let's think about a derive property (a combination of firstname & lastname)
[NotMapped]
public string FullName { get { return $"{FirstName} {LastName}"; } }
}
Unfortunately, after creating the app I saw a typo mistake in Email => Emanil.
Now after correcting the typo I try: Add-Migration EmailUpdate but it fails, I do try Update-Database as well still fail, I make corrections on each line were I have Emanil to Email:
public async Task<IActionResult> Create([Bind("FirstName,LastName,Address1,Address2,City,State,Zip,Emanil,Phone,Created,ImageData,ImageType,ImageFile,Id")] Contact contact)
Just want to know the best way to update it (I use Visual Studio for the project).
I have a MachineSettings entity and this entity has a Machine entity as a foreign key.
public class Machine
{
[Key]
public int MachineId { get; set; }
[Required]
[Display(Name = "Machine Type")]
public string MachineName { get; set; }
}
public class MachineSettings
{
[Key]
public int MachineSettingId { get; set; }
[Required]
public string Password { get; set; }
[Required]
public int ReferenceKeyLength { get; set; }
[Required]
public virtual Machine MachineId { get; set; }
}
In my controller class, when I created an Edit method as POST:
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Edit(MachineSettings machineSettings)
{
if (ModelState.IsValid)
{
db.Entry(objapp).State = EntityState.Modified;
db.SaveChanges();
return RedirectToAction("Index");
}
return View(machineSettings);
}
On the page I have shown the list of machines in drop down list, when do the changes and select another machine in dropdown and click on save. The Edit method is called with a MachineSetting object. Here my ModelState.IsValid always false.
On checking the object in debug mode, I am getting the MachineId from the drop down, but MachineName is not returned, so how to avoid MachineName checking in this so that ModelState.IsValid property will be true?
try to fix the classes
public class MachineSettings
{
[Key]
public int MachineSettingId { get; set; }
[Required]
public string Password { get; set; }
[Required]
public int ReferenceKeyLength { get; set; }
[Required]
public int? MachineId { get; set; }
public virtual Machine Machine { get; set; }
}
public class Machine
{
[Key]
public int MachineId { get; set; }
[Required]
[Display(Name = "Machine Type")]
public string MachineName { get; set; }
public virtual ICollection<MachineSettings> MachineSettings { get; set; }
}
and create view model for the view
public class MachineViewModel
{
public MachineSettings MachineSettings {get; set;}
public IEnumerable<SelectListItem> Machines { get; set; }
}
and action
public ActionResult Edit(MachineViewModel viewModel)
Maybe you should use a specific class for your ViewModel. ViewModels are for Views(UI) <--> Controller intercommunication, and usually are diferrent classes than the persistance (EF) layer. So your ViewModel doesn't even needs a full Machine property. Could be something like that:
public class MachineSettingsViewModel
{
public int MachineSettingId { get; set; } // Only if you are using the ViewModel for edit
[Required]
public string Password { get; set; }
[Compare("Password")]
public string ConfirmPassword { get; set; }
[Required]
public int ReferenceKeyLength { get; set; }
[Required]
public int MachineId { get; set; }
public IEnumerable<SelectListItem> Machines { get; set; } // Collection with the options for the machine selector. Must be filled in the controller from query to service or DB
}
The ViewModel may include properties only used in the view, or needed for form validation but not needed in the DB.
In the controller "Get" action, you create and fill the viewmodel, then you pass it yo the view, and in the "Post" method you validate the viewmodel and convert it to a entity to be saved in the DB.
Take a look at What is ViewModel in MVC?. It's a more detailed explanation of ViewModels vs Models.
I'm new to mongo and I'm looking for some guidance on best practices when dealing with referenced relationships and modeling them in .net core.
Yes, it's the usual "joins in mongodb??" question. But I haven't found a good answer to this.
To make this simple, lets say I've got a simple API i'm building out with controllers to manage users and accounts.
In mongo there are two collections, Accounts and Users. Users belong to their parent Account. I don't want to go the embedded document route in this case, so each User document will have an AccountID in it to link the user back to their parent Account.
My current entities in .net are:
public class User
{
[BsonId]
[BsonRepresentation(BsonType.ObjectId)]
public string Id { get; set; }
[BsonElement("firstName")]
public string FirstName { get; set; }
[BsonElement("lastName")]
public string LastName { get; set; }
[BsonElement("email")]
public string Email { get; set; }
[BsonElement("status")]
public string Status { get; set; }
[BsonElement("type")]
public string Type { get; set; }
[BsonElement("createdDateTime")]
public DateTime CreatedDateTime { get; set; }
[BsonElement("modifiedDateTime")]
public DateTime ModifiedDateTime { get; set; }
[BsonRepresentation(BsonType.ObjectId)]
[BsonElement("accountId")]
public string AccountId { get; set; }
}
public class Account
{
[BsonId]
[BsonRepresentation(BsonType.ObjectId)]
public string Id { get; set; }
[BsonElement("name")]
public string Name { get; set; }
[BsonElement("status")]
public string Status { get; set; }
[BsonElement("type")]
public string Type { get; set; }
[BsonElement("createdDateTime")]
public DateTime CreatedDateTime { get; set; }
[BsonElement("modifiedDateTime")]
public DateTime ModifiedDateTime { get; set; }
}
Those are then mapped to models using AutoMapper in the controller
public class UserModel
{
[Required]
public string FirstName { get; set; }
[Required]
public string LastName { get; set; }
[Required]
public string Email { get; set; }
[Required]
public string Status { get; set; }
[Required]
public string Type { get; set; }
[Required]
public DateTime CreatedDateTime { get; set; }
[Required]
public DateTime ModifiedDateTime { get; set; }
[Required]
public string AccountId { get; set; }
}
public class AccountModel
{
[Required]
public string Name { get; set; }
[Required]
public string Status { get; set; }
[Required]
public string Type { get; set; }
[Required]
public DateTime CreatedDateTime { get; set; }
[Required]
public DateTime ModifiedDateTime { get; set; }
}
And an example of a controller method where the mapper is used:
[HttpGet]
public async Task<ActionResult<List<AccountModel>>> Get()
{
try
{
var results = await _repository.Get();
return _mapper.Map < List < AccountModel >>(results);
}
catch (Exception ex)
{
return this.StatusCode(StatusCodes.Status500InternalServerError, "Database Failure");
}
}
All that works just fine. I can call the controller methods, get the data, and it gets mapped from entity to model and then returned from the controller method.
The issue is this: I'd like to return the user data with information from the account (example: account name). So just a simple join.
I think I have an handle on how to do the join itself, using one of the methods outlined in this answer. But my question is, is there a best practice on how to set up my entities and models to make storing this as clean as possible?
I was thinking of adding a property to the User entity to store the related account. Tagged with the [BsonIgnore] attribute so that it stays out of the db.
[BsonIgnore]
public Account Account { get; set; }
The property
[BsonRepresentation(BsonType.ObjectId)]
[BsonElement("accountId")]
public string AccountId { get; set; }
would still remain in the user entity, so the reference is preserved.
Then, the User model could have properties like
public string AccountName { get; set; }
They get populated using the mapper.
Is this the best way to set this up when you want to reference related objects rather then embed them? Is there some gotcha here I'm missing?
have a look at the code below. it uses my library MongoDB.Entities which has built-in support for one-to-one, one-to-many and many-to-many relationships between entities.
using MongoDB.Entities;
using System.Linq;
namespace StackOverflow
{
public class Program
{
public class Account : Entity
{
public string Name { get; set; }
public Many<User> Users { get; set; }
public Account() => this.InitOneToMany(() => Users);
}
public class User : Entity
{
public string FirstName { get; set; }
public string LastName { get; set; }
public One<Account> Account { get; set; }
[Ignore]
public string AccountName { get; set; }
}
private static void Main(string[] args)
{
new DB("test");
var account = new Account { Name = "parent account" };
account.Save();
var user = new User
{
FirstName = "dave",
LastName = "mathews",
Account = account.ToReference()
};
user.Save();
account.Users.Add(user);
//find parent by ID
var parent = DB.Find<Account>().One(account.ID);
//get first user of parent
var dave = parent.Users.ChildrenQueryable()
.FirstOrDefault();
//get dave's account
var davesAccount = dave.Account.ToEntity();
//get dave with account name filled in by a single mongo query
var daveExtra = (from u in DB.Queryable<User>().Where(u => u.ID == dave.ID)
join a in DB.Queryable<Account>() on u.Account.ID equals a.ID
select new User
{
ID = u.ID,
FirstName = u.FirstName,
LastName = u.LastName,
AccountName = a.Name
}).SingleOrDefault();
}
}
}
I have been trying to make this happen for a few hours now.I have two models within my model folder called Models, i am trying to pull data from the model to display it in the view, i am aware of only one model statement can be applied to the view. So i have Created a ViewModels which contain the properties that i would like to reference in the view. now when i run the the application im getting a compilation Error which says:
"Compiler Error Message: CS0246: The type or namespace name 'Models' could not be found (are you missing a using directive or an assembly reference?)"
I would appreciate a bit of help of if there is any other way of doing it feel free to advice.
New ViewModel created
public class MainModelscs <T> where T :class
{
public StoreAudit StoreAudit { get; set; }
public StoreQuestions StoreQuestions { get; set; }
public List<string> StoreWindow { get; set; }
public IPagedList<T> IndexList { get; set; }
}
ViewMode PROPERTIES inside my view,
#model PopMarketing.ViewModel.MainModelscs<PopMarketing.Models>
Model 1
public class StoreQuestions
{
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
[Key]
public int ReviewId { get; set; }
public int AuditId { get; set; }
public int QuestionOne { get; set; }
public string QuestionTwo { get; set; }
public string QuestionThree { get; set; }
public string QuestionFour { get; set; }
}
Model 2
[Key]
[Column(Order = 1)]
public int AuditId { get; set; }
public string Date { get; set; }
public int StoreNumber { get; set; }
public string StoreName { get; set; }
public string Address { get; set; }
public string Manager { get; set; }
Controller Method
public class AuditController : Controller
{
private PopMarketingContext db = new PopMarketingContext();
//
// GET: /Audit/
private const string PASSWORD = "MarchJava2016";
public ActionResult Index(string searchString)
{
int number;
int check;
bool result = Int32.TryParse(searchString, out number);
if (result)
{
check = Convert.ToInt32(searchString);
var shops = from s in db.StoreAudit
select s;
if (!String.IsNullOrEmpty(searchString))
{
shops = shops.Where(s => s.StoreName.ToUpper().Contains(searchString.ToUpper()) ||
s.StoreNumber.Equals(check));
}
return View(shops.ToList());
}
else
{
var shops = from s in db.StoreAudit
select s;
if (!String.IsNullOrEmpty(searchString))
{
shops = shops.Where(s => s.StoreName.ToUpper().Contains(searchString.ToUpper()));
}
return View(shops.ToList());
}
}
Jaimin is correct, in #model you pass the class you want to use as model, not the directory or namespace of the class.
As for using viewmodels correctly:
Let's say I have two classes:
class Student
{
public string name;
public string mentor;
}
class Teacher
{
public string name;
public string salary;
}
And i want to have a view show both their names. I can create a viewmodel like this:
class StudentAndTeacherVM
{
public Teacher teacher;
public Student student;
}
The controller would look like this:
public ActionResult Index()
{
var student = new Student();
*fill student*
var teacher = new Teacher();
*fill teacher*
var model = new PersonVM();
model.teacher = teacher;
model.student = student;
return view(model);
}
Now you can reach them in the view by:
#Model.student.name
#Model.teacher.name
Or if I want to be as efficient as possible, I could also just put in the names into the VM.
controller:
public ActionResult Index()
{
var student = new Student();
*fill student*
var teacher = new Teacher();
*fill teacher*
var model = new PersonVM();
model.teacherName = teacher.name;
model.studentName= student.name;
return view(model);
}
View:
class StudentAndTeacherVM
{
public string teacherName;
public string studentName;
}
Create instance of MainModelscs in your action
. . .
i.e. MainModelscs <string> Obj = new MainModelscs <string> ;
. . .
Then pass this to the view.
return View(Obj);
Now in your view,
#model PopMarketing.ViewModel.MainModelscs<string>
(take T as your model name not the namespace!)
Sorry, i got much late than i said. But here is how you can join two tables, map them to a model and pass to the view. The first one of the class whose properties correspond to the comments table of the database.
public class Comments
{
public int ID { get; set; }
public string CommentText { get; set; }
public string Username { get; set; }
public int Upvote { get; set; }
public int Downvote { get; set; }
public int InappropriateFlags { get; set; }
public int PostId { get; set; }
public string ControllerName { get; set; }
public int ReplyTo { get; set; }
public bool replied { get; set; }
}
Now another user profiles table which is needed to be joined with the comments table.
public class UserProfile
{
public int ID { get; set; }
public string Username { get; set; }
public string DisplayName { get; set; }
public int Reputation { get; set; }
public DateTime DateOfRegistration { get; set; }
public int Money { get; set; }
public string ImageUrl { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string Country { get; set; }
public string State { get; set; }
public string City { get; set; }
public string Location { get; set; }
public int? PostalCode { get; set; }
public int ProfileViews { get; set; }
public string AboutMe { get; set; }
}
Now the much needed model for handling the join of these two tables. I haven't included all the properties of both tables as i didn't need them in the view, but you can ofcourse include them. In that case, don't forget to update your data access query also.
public class CommentUserProfileJoin
{
public int ID { get; set; }
public string CommentText { get; set; }
public string Username { get; set; }
public string DisplayName { get; set; }
public int Upvote { get; set; }
public int Downvote { get; set; }
public int InappropriateFlags { get; set; }
public int PostId { get; set; }
public string ControllerName { get; set; }
public int ReplyTo { get; set; }
public bool replied { get; set; }
public int UserProfileId { get; set; }
}
I am using dapper ORM. So this is the data access code that creates a join and maps it to the join table written above.
public IEnumerable<CommentUserProfileJoin > GetComments(int postId, string controllerName, int replyTo)
{
IEnumerable<CommentUserProfileJoin> comments;
const string query = "Select c.[id], c.[CommentText], c.[Username], c.[Upvote], c.[Downvote], c.[InappropriateFlags], c.[PostId], c.[ControllerName], c.[ReplyTo], c.[replied], u.[id] as UserProfileId, u.displayname from Main.Comments c LEFT JOIN Profile.UserProfiles u on c.username = u.username where c.PostId = #postId and c.ControllerName = #contName and c.ReplyTo = #ReplyTo order by ID desc";
comments = conObj.Query<CommentUserProfileJoin>(query, new { postId = postId, contName = controllerName, ReplyTo = replyTo });
return comments;
}
so now i have the model for the join of two tables, i can use it in any view like this
#model IEnumerable<DataAccessLayer.Models.CommentUserProfileJoin>
Hope this helps you. Also, unintentionally, i may not have followed best practices. I will be glad if someone can notify them through comments. Thanks
In class I have this :
public class CustomerMvc
{
public int Id { get; set; }
[Required(ErrorMessage = "LastName mandatory.")]
public string LastName { get; set; }
[EmailValidation(ErrorMessage = "Email not valid.")]
public string Email { get; set; }
}
In another class, I have this :
public class CartMvc
{
public CustomerMvc Customer { get; set; }
[Required(ErrorMessage = "VAT mandatory.")]
public int VatId { get; set; }
}
A Save method int the controller, receive a model type CartMvc. The problem is, in this case, I don't want validate the property type CustomerMvc but only VatId.
Is there a way to bypass, in this case, the validation on CustomerMvc ? Other way ?
Thanks,
You could use a view model:
public class SaveCustomerMvcViewModel
{
public int Id { get; set; }
public string LastName { get; set; }
public string Email { get; set; }
}
and then:
public class SaveCartMvcViewModel
{
public SaveCustomerMvcViewModel Customer { get; set; }
[Required(ErrorMessage = "VAT mandatory.")]
public int VatId { get; set; }
}
Now of course your Save controller action will take the appropriate view model as parameter:
[HttpPost]
public ActionResult Save(SaveCartMvcViewModel model)
{
...
}
And as a side remark, putting the [Required] attribute on a non-nullable integer property (your VatId property) hardly makes any sense because a non-nullable integer will always have a value. If you want to validate that the user actually entered some value you'd better use a nullable integer on your view model:
public class SaveCartMvcViewModel
{
public SaveCustomerMvcViewModel Customer { get; set; }
[Required(ErrorMessage = "VAT mandatory.")]
public int? VatId { get; set; }
}