I have a problem with implementing the CRUD operations by using repositories, view models, and mapping in my ASP.Net MVC project. The "details"(read the information about one object) and "index"(read the whole list of objects), controllers are working.
I am mapping the Model to ViewModel and then display it in View. But for Create, Update and Delete operations, I should map the ViewModel to the Model. Could you tell me where am I wrong?
Model
public class User
{
[Key]
public int Id { get; set; }
[Required]
public string Name { get; set; }
[Unique]
[Required]
[MaxLength(100)]
public string Email { get; set; }
[Required]
public string Password { get; set; }
public string Phone { get; set; }
public bool IsAdmin { get; set; }
}
Base repository
public class BaseRepository<T> : IBaseRepository<T> where T : class
{
private RushHourContext db = null;
private DbSet<T> table = null;
public BaseRepository()
{
this.db = new RushHourContext();
table = db.Set<T>();
}
public BaseRepository(RushHourContext db)
{
this.db = db;
table = db.Set<T>();
}
public IEnumerable<T> SelectAll()
{
return table.ToList();
}
public T SelectByID(object id)
{
return table.Find(id);
}
public void Insert(T obj)
{
table.Add(obj);
}
public void Update(T obj)
{
table.Attach(obj);
db.Entry(obj).State = EntityState.Modified;
}
public void Delete(object id)
{
T existing = table.Find(id);
table.Remove(existing);
}
public void Save()
{
db.SaveChanges();
}
}
Interface for Repository
public interface IBaseRepository<T> where T : class
{
IEnumerable<T> SelectAll();
T SelectByID(object id);
void Insert(T obj);
void Update(T obj);
void Delete(object id);
void Save();
}
Controller
private RushHourContext _db = new RushHourContext();
private IBaseRepository<User> _repository = null;
public UsersController()
{
this._repository = new BaseRepository<User>();
}
public ActionResult Index()
{
if (!LoginUserSession.IsStateAdmin)
{
return RedirectToAction("Login");
}
var users = _repository.SelectAll().ToList();
var userViewModel = Mapper.Map<List<UserViewModel>>(users);
return View(userViewModel);
}
public ActionResult Details(int? id)
{
var users = _repository.SelectByID(id);
var userViewModel = Mapper.Map<UserViewModel>(users);
return View(userViewModel);
}
public ActionResult Create(User user)
{
var users = _repository.Insert(user); // THIS CODE HERE IS WRONG
var userViewModel = Mapper.Map<User>(users);
return View(userViewModel);
}
UserViewModel
public class UserViewModel
{
public int Id { get; set; }
[Required(ErrorMessage = "Please enter User Name.")]
[Display(Name = "User Name")]
public string Name { get; set; }
[MaxLength(100)]
[Display(Name = "Email Address")]
public string Email { get; set; }
[Required]
[Display(Name = "Password")]
public string Password { get; set; }
public string Phone { get; set; }
public bool IsAdmin { get; set; }
}
View
#model RushHour.ViewModels.UserViewModel
#{
ViewBag.Title = "Create";
}
<h2>Create</h2>
<div>
#Html.AntiForgeryToken()
#using (Html.BeginForm("Create", "Users", FormMethod.Post, new { enctype = "multipart/form-data" }))
{
#Html.AntiForgeryToken()
<div>#Html.LabelFor(model => model.Id)</div>
<div>#Html.TextBoxFor(model => model.Id)</div>
<div>#Html.ValidationMessageFor(model => model.Id)</div>
<div>#Html.LabelFor(model => model.Name)</div>
<div>#Html.TextBoxFor(model => model.Name)</div>
<div>#Html.ValidationMessageFor(model => model.Name)</div>
<div>#Html.LabelFor(model => model.Password)</div>
<div>#Html.TextBoxFor(model => model.Password)</div>
<div>#Html.ValidationMessageFor(model => model.Password)</div>
<div>#Html.LabelFor(model => model.Email)</div>
<div>#Html.TextBoxFor(model => model.Email)</div>
<div>#Html.ValidationMessageFor(model => model.Email)</div>
<div>#Html.LabelFor(model => model.Phone)</div>
<div>#Html.TextBoxFor(model => model.Phone)</div>
<div>#Html.ValidationMessageFor(model => model.Phone)</div>
<div>#Html.LabelFor(model => model.IsAdmin)</div>
<div>#Html.TextBoxFor(model => model.IsAdmin)</div>
<div>#Html.ValidationMessageFor(model => model.IsAdmin)</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="Create" class="btn btn-default" />
</div>
</div>
}
</div>
Your model in your Create view is your UserViewModel and that should be passed into the controller, not User.
The pattern you have set up suggests the user will first be on the Create view while entering new user information. So, they will navigate to this view without passing any objects to it on their first trip. If that's the case, you'll want a parameterless constructor for the first visit where you'll just create a new UserViewModel and pass it in. You're then navigating back to this view after user creation which will present a form to them and be very confusing for user experience. You'll probably want to redirect to a confirmation page or the login page with a message saying user created.
Whenever you make changes to the data, EntityFramework won't make those changes until you call SaveChanges on the repository. It's odd that you aren't saving your new user immediately after it being created.
DbSet.Add
DbContext.SaveChanges
For the create function use UserViewModel instead of User. Then map from UserViewModel to User to insert in database. After, if you want to display the saved data, then return UserViewModel object.
Try the following code.
[HttpPost]
public ActionResult Create(UserViewModel userViewModel)
{
var user = new User();
var newUser = Mapper.Map(userViewModel, user);
_repository.Insert(newUser);
_repository.Save();
return View(userViewModel);
}
you can do like this:
[HttpPost]
public ActionResult Create(UserViewModel userViewModel)
{
var user = Mapper.Map<User>(userViewModel);
_reoisitory.Add(user);
}
Related
In my Razor project I have two classes as follows:
namespace reftest.Models
{
public class Details
{
public int Id { get; set; }
public int Age { get; set; }
public int IQ { get; set; }
}
public class Person
{
public int Id { get; set; }
public string Name { get; set; }
public Details Particulars { get; set; }
}
}
I added a Scaffolding Item as per the tutorial which created Create, Delete, Details, Edit and Index pages as usual. These work correctly as far as the Name field is concerned. I can create new Person records, edit them, etc. Age and IQ were not displayed. No problem - I went into the Create.cshtml and added the following:
<div class="form-group">
<label asp-for="Person.Particulars.Age" class="control-label"></label>
<input asp-for="Person.Particulars.Age" class="form-control" />
<span asp-validation-for="Person.Particulars.Age" class="text-danger"></span>
</div>
For the corresponding controller I changed nothing but here it is for reference:
public class CreateModel : PageModel
{
private readonly reftest.Models.reftestContext _context;
public CreateModel(reftest.Models.reftestContext context)
{
_context = context;
}
public IActionResult OnGet()
{
return Page();
}
[BindProperty]
public Person Person { get; set; }
public async Task<IActionResult> OnPostAsync()
{
if (!ModelState.IsValid)
{
return Page();
}
_context.Person.Add(Person);
await _context.SaveChangesAsync();
return RedirectToPage("./Index");
}
}
It displayed a text box for Age and when I entered a value and submitted the new record, it was preserved. I know this by looking at the DB records where I can see a row in the Details table and a corresponding (and correct) Id number for that record in the Particulars column of the Person table.
However, no matter what I do I cannot retrieve it as part of the Person model. The value for Particulars is always Null. For example on the Details page I have this:
<dl class="dl-horizontal">
<dt>
#Html.DisplayNameFor(model => model.Person.Particulars.Age)
</dt>
<dd>
#Html.DisplayFor(model => model.Person.Particulars.Age)
</dd>
</dl>
which always displays an empty field. In the corresponding .cshtml.cs file I have this:
public class DetailsModel : PageModel
{
private readonly reftest.Models.reftestContext _context;
public DetailsModel(reftest.Models.reftestContext context)
{
_context = context;
}
public Person Person { get; set; }
public async Task<IActionResult> OnGetAsync(int? id)
{
if (id == null)
{
return NotFound();
}
Person = await _context.Person.FirstOrDefaultAsync(m => m.Id == id);
if (Person == null)
{
return NotFound();
}
return Page();
}
}
Person.Particulars is always returned as null. It simply doesn't populate it. But I can see the correct data and foreign key in the database. How can I get it to populate?
I've been struggling with this problem for a while now and I can't find any solution on that solves it for me.
I'm trying to add a string to my model in a view, however when the model gets returned to my HttpPost everything is null except for the string that I'm trying to fill
My model looks like this
namespace WhatsUp.Models {
public class ChatModel
{
public Account user { get; set; }
public Contact contact { get; set; }
public Chat chatA { get; set; }
public Chat chatB { get; set; }
public string newMessage { get; set; }
public ChatModel() { }
public ChatModel(Account user, Contact contact, Chat chatA, Chat chatB)
{
this.user = user;
this.contact = contact;
this.chatA = chatA;
this.chatB = chatB;
}
}
}
My controller looks like this
namespace WhatsUp.Controllers
{
public class ChatsController : Controller
{
IMessageRepository repository = new DbMessageRepository();
IContactRepository contactRepository = new DbContactRepository();
IAccountRepository accountRepository = new DbAccountRepository();
IChatRepository chatRepository = new DbChatRepository();
// GET: Chats
public ActionResult Index()
{
return View();
}
public ActionResult Chat(int contactId)
{
Account user = accountRepository.GetAccount(User.Identity.Name);
Contact contact = contactRepository.GetContact(contactId);
Chat chatA = chatRepository.GetChat(user.id, contact.accountId ?? default(int));
if(chatA == null)
{
chatRepository.CreateChat(user.id, contact.accountId ?? default(int));
}
Chat chatB = chatRepository.GetChat(contact.accountId ?? default(int), user.id);
if(chatB == null)
{
chatRepository.GetChat(user.id, contact.accountId ?? default (int));
}
ChatModel chatModel = new ChatModel(user, contact, chatA, chatB);
return View(chatModel);
}
[HttpPost]
public ActionResult Chat(ChatModel chatModel)
{
repository.SendMessage(new Message(0, chatModel.newMessage, chatModel.chatA.Id));
ModelState.Clear();
return View(chatModel);
}
}
}
And my view
#using WhatsUp.Models
#model ChatModel
#{
ViewBag.Title = "Chat";
}
<h2>Chat with #Model.contact.name</h2>
<div id="chatWindow" style="overflow-y:auto; overflow-x:hidden; height:500px;">
<script>
var element = document.getElementById('chatWindow');
element.scrollTop = element.offsetHeight
</script>
</div>
#using (Html.BeginForm())
{
#Html.TextAreaFor(x => x.newMessage, new { #class = "form-control"})
<input type="submit" value="Send" class="btn btn-primary" />
}
Always use a ViewModel for that kind of things. Follow this steps:
First, create a ViewModel that will contain your message and the chat ids.
Second, ake the ids as a hidden field in your view.
Last thing is, in your POST action, to make sure to get each Chat instance via their id before sending the message.
I am developing a simple mvc application . The code is as follows:
Model .cs:
public class CustomModel
{
public IEnumerable<lang> lstlang { get; set; }
public IEnumerable<org> lstOrg { get; set; }
}
public class lang
{
public int langid { get; set; }
public string langName { get; set; }
}
public class org
{
public int orgId { get ;set;}
public string orgName { get; set; }
}
Controller.cs
public Action Index()
{
// Get data from database and fill the model
var model = new CustomModel();
return View(model);
}
public Action Partial()
{
// Get data from database and fill the model
var model = new CustomModel();
return PartialView(model);
}
[HttpPost]
public Action Partial(FormCollection frm, CustomModel model)
{
// Get data from database and fill the model
var model = new CustomModel();
return PartialView(model);
}
Index.cshtml
#model CustomModel
#Html.TextboxFor(x => x.lang.FirstOrDefault().id);
<input type="button" id="btn" />
#Html.RenderPartial("Partial", model)
Partial.cshtml
#model CustomModel
#Html.TextboxFor(x => x.lang.FirstOrDefault().id);
<input type="submit" id="submit" />
The thing is, when I click the submit button in the Partial.cshtml page, and examine the model in httppost method in public Action Partial(FormCollection frm, CustomModel model), the model contains null for both lists lstlang and lstOrg, but the formcollection[0] will give the selected textbox value.
What am I missing, or is this the right way of using partial views?
Don't use FirstOrDefault(). If you want to post something back to the front end with collections, you'll need to use indexing.
Public class CustomModel
{
public ICollection<lang> lstlang { get; set; }
public ICollection<org> lstOrg { get; set; }
}
#HTML.textboxfor(x=>x.lang[0].id);
I have a class Client that have some properties in particular one is restriction_type. Also, I create another class Restriction with an ID and a name properties. The name property correspond to the restriction_type.
Then I display the name of all restrictions in my database in the dropdown list:
#Html.ActionLink("Create New", "Create")
#using (Html.BeginForm("AddRestrictions","Restrictions",FormMethod.Get)){
<p> Type de restriction:
#Html.DropDownList("ClientRestr_type", "All")
</p>
<input type="submit"value="Ajouter"/>
}
That is my controller:
public ActionResult AddRestriction(string ClientRestr_type, Restriction restriction)
{
var RestrLst = new List<string>();
var RestrQry = from d in db.Restrictions
orderby d.name
select d.name;
RestrLst.AddRange(RestrQry.Distinct());
ViewBag.ClientRestr_type = new SelectList(RestrLst);
var clients = from c in db.Restrictions select c;
if (string.IsNullOrEmpty(ClientRestr_type))
return View();
else
{
if (ModelState.IsValid)
{
// Here I have maybe to find the way to solve my problem
}
}
So I want to add the name property of Restriction in the restriction_type property of my Model Client.
Model Client:
public class Client
{
[Required]
public int ID
{
get;
set;
}
[Required]
public string compte
{
get;
set;
}
[Required]
public string portefeuille
{
get;
set;
}
[Required]
public String restriction_type
{
get;
set;
}
[Required]
public Boolean etat
{
get;
set;
}
public Boolean decision
{
get;
set;
}
Model Restriction:
public class Restriction
{
public int restrictionID
{
get;
set;
}
public string name
{
get;
set;
}
}
What do you think about my GetRestrictions() method
private SelectList GetRestrictions()
{
var RestrLst = new List<string>();
var RestrQry = from d in db.Restrictions
orderby d.name
select d.name;
RestrLst.AddRange(RestrQry.Distinct());
return new SelectList(RestrLst);
}
But unfortunately I have an error: Impossible to convert System.Web.Mvc.SelectList to MyApp.Models.Client at line:
model.RestrictionList = GetRestrictions();
I don't understand why
Thank you for your help!
A simplified example:
View model
public class ClientVM
{
public Client Client { get; set; }
public SelectList RestrictionList { get; set; }
}
Controller
[HttpGet]
public ActionResult Create()
{
ClientVM model = new ClientVM();
model.Client = new Client();
model.RestrictionList = GetRestrictions(); // your code to return the select list
return View("Edit", model);
}
[HttpGet]
public ActionResult Edit(int ID)
{
ClientVM model = new ClientVM();
model.Client = // call database to get client based on ID
model.RestrictionList = GetRestrictions();
return View(model);
}
[HttpPost]
public ActionResult Edit(ClientVM model)
{
if (!ModelState.IsValid)
{
model.RestrictionList = GetRestrictions();
return View(model);
}
Client client = model.Client;
// Save and redirect
....
}
View
#model YourNamespace.ClientVM
#using (Html.BeginForm() {
#Html.TextBoxFor(m => m.Client.ID)
#Html.TextBoxFor(m => m.Client.compte)
...
#Html.DropDownListFor(m => m.Client.restriction_type, Model.RestrictionList)
<input type="submit" value="Save" />
}
I am learning LINQ and was wondering how can I INSERT into two different tables with no relationship on one click using LINQ. Is it even possible?
I am adding into one table like this. How can I add into the second table as well?
Second Table,
GenreId
Name
Description
Code,
[HttpPost]
public ActionResult Create(Artist artist)
{
if (ModelState.IsValid)
{
_db.Artists.Add(artist);
_db.Genres.Add(new Genre { GenreId = 1, Name = "Some Genre", Description = "Trying to add second table" }); // how can i add the genre object here
_db.SaveChanges();
return RedirectToAction("Index", new { id = artist.ArtistId });
}
return View(artist);
}
Note that my View is strongly typed to Artist class.
My View,
#model EntityFrameWorkDBFirst.Models.Artist
#Html.TextBoxFor(model => model.Name)
#Html.TextBoxFor(model =>model.Genres) // how to get this working
My Model Class,
public partial class Artist
{
public Artist()
{
this.Albums = new HashSet<Album>();
this.Genres = new HashSet<Genre>();
}
public int ArtistId { get; set; }
public string Name { get; set; }
public virtual ICollection<Album> Albums { get; set; }
public virtual ICollection<Genre> Genres { get; set; }
}
Tuple related suggestion didn't work. Maybe I am missing something.
#model Tuple<EntityFrameWorkDBFirst.Models.Artist, EntityFrameWorkDBFirst.Models.Genre>
<legend>Create a Artist</legend>
#Html.TextBoxFor(model => model.Item1.Name)
<h2>Genre</h2>
#Html.TextBoxFor(model => model.Item2.Name)
#Html.TextBoxFor(model => model.Item2.Name)
<p>
<input type="submit" value="Create" />
</p>
Controller Code:
[HttpPost]
public ActionResult Create(Artist artist, Genre genre)
{
if (ModelState.IsValid)
{
_db.Artists.Add(artist);
_db.Genres.Add(genre);
_db.SaveChanges();
return RedirectToAction("Index", new { id = artist.ArtistId });
}
return View(artist);
}
You mixed domain model and view model.
It's big mistake, you should work only with viewmodel on view.
You should create view model:
public class CreateArtistViewModel
{
public string ArtistName { get; set; }
public int? Genres { get; set; } // if you provide chooser for user
public string GenresName { get; set; } // if user can enter new genre
public string GenresDecription { get; set; } // if user can enter new genre
public IList<Genre> Genres { get; set; }
}
in the post action you should check view model and create artist and genre if user create new gener.