MVC4 passing same parameters to multiple actions on controller - asp.net-mvc-4

I am creating an MVC4 application that has an index page that shows a grid. The index action for the page has parameters for a search string, sort order, account id, and vendor id that control what is shown.
public class MyController
{
public ActionResult Index(string searchFilter="",
string sortOrder="createddate",
int accountid=0,
int vendorid=0)
{
...
}
}
From this main page I can click on rows in the grid, which take me to the create and edit actions. In addition, I have several other actions on the control that are called from the index page.
When I press Save or Cancel on any of these views (the [HttpPost] action, as usual they redirect to the index action. But these views don't have the search string, sort order, account id, and vendor id so that the index view can "restore" what was being shown.
So I have been adding these parameters to the other actions methods, and making an object with the properties needed to in the view models for the methods.
public class PageFeatures
{
public string SearchFilter {get; set;}
public string SortOrder {get; set;}
public int AccountId {get; set;}
public int VendorId {get; set;}
}
public class CreateViewModel
{
public PageFeatures Features { get; set; }
}
public class EditViewModel
{
public EditViewModel(int id)
{
Id=id;
}
public int Id {get; set;}
public PageFeatures Features { get; set; }
}
public class MyController
{
public ActionResult Index(string searchFilter="",
string sortOrder="createddate",
int accountid=0,
int vendorid=0)
{
...
}
public ActionResult Create(string searchFilter="",
string sortOrder="createddate",
int accountid=0,
int vendorid=0)
{
var model = new CreateViewModel();
model.Features = new PageFeatures
{
SearchFilter = searchFilter,
SortOrder = sortOrder,
AccountId = accountid,
VendorId = vendorid
}
return View(model);
}
[HttpPost]
public ActionResult Create(CreateViewModel model)
{
if(ModelState.IsValid)
{
....
return RedirectToAction("Index", new {searchFilter=model.Features.SearchFilter,
sortOrder = model.Features.SortOrder,
accounted = model.Features.AccountId,
vendorid = model.Features.VendorId} );
}
}
public ActionResult Edit( int id=0,
string searchFilter="",
string sortOrder="createddate",
int accountid=0,
int vendorid=0)
{
var model = new EditViewModel(id);
model.Features = new PageFeatures
{
SearchFilter = searchFilter,
SortOrder = sortOrder,
AccountId = accountid,
VendorId = vendorid
}
return View(model);
}
[HttpPost]
public ActionResult Edit(EditViewModel model)
{
if(ModelState.IsValid)
{
....
return RedirectToAction("Index", new {searchFilter=model.Features.SearchFilter,
sortOrder = model.Features.SortOrder,
accounted = model.Features.AccountId,
vendorid = model.Features.VendorId} );
}
}
}
But what I don't like about this approach is that I am peppering my views and every controller method with these values.
Am I doing the right thing, or is there a better way, like an action filter that would make this stuff easier and less repetitive?

[HttpPost]
public ActionResult Edit(EditViewModel model)
{
if(ModelState.IsValid)
{
....
return RedirectToAction("Index", new {searchFilter=model.Features.SearchFilter,
sortOrder = model.Features.SortOrder,
accounted = model.Features.AccountId,
vendorid = model.Features.VendorId} );
}
}
You are using the right approach.
If you dont want your URL to be ugly filled with query string parameters. You can try storing these values in a session and retrieving them in each action of your controller. Set them once and your done to use it anywhere, if you want to change the session values you can just over write it

Have you thought about using TempData and setting these params in there. The best part is that you can persist TempData across Controllers and you can perhaps modify the TempData values in a base controller class.

Related

Id is always 0 when creating a new Order

I'm working with .net Core 5 and MVC
I'm trying to create a form to fill out Customer information to proceed with Ordering, everything is fine but the order Id generated is always 0. I Don't know where the problem lies. Can someone help me with the problem I'm having?
Here is my Repository
public bool CreateOrder(Order order)
{
var newOrder = new Order
{
FirstName = order.FirstName,
LastName = order.LastName,
Email = order.Email,
PhoneNumber = order.PhoneNumber,
//more
};
_dbContext.Orders.Add(newOrder);
_dbContext.SaveChanges();
return true;
}
Here is my Post method
public IActionResult CreateOrder(Order order)
{
var items = _cartRepo.GetCartItem();
if (items == null)
{
ModelState.AddModelError("", "Add Product First");
}
else
{
_cartRepo.CreateOrder(order);
}
return View();
}
Model
public partial class Order
{
[Key]
public int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string Email { get; set; }
// More
}
Maybe you are missing this in your createOrder view inside the form:-
<input hidden asp-for="Id" />
Do you use SSMS? If yes, go to the menu Tools > Options > Designers and uncheck Prevent Saving changes that require table re-creation.

Conflict Variable Value in Get/POST

I have created a asp.net mvc application which has Get and Post method like
public ActionResult MyData(string companyid)
{
MyModel model= new MyModel ();
model = (LINQ) ;
return View(model);
}
[HttpPost]
public ActionResult MyData(MyModel model)
{
if(model.companyid>0)
{
//Update Database
}
else
{
// insert database
}
return View(model);
}
public class MyModel
{
public int companyid {set; get;}
public string Name {set; get;}
public string Address {set; get;}
}
Now my question is: I am getting model.companyid =0 always in post i.e. # if(model.companyid>0)
when I update the get method
from public ActionResult MyData(string companyid)
to public ActionResult MyData(string cid)
it works.
i.e. rename companyid to cid, why it happens. Please let me know...I think, we have seperate method for get and post.

Getting the string-value from foreign key

I got two classes in my blog-app:
public class Post
{
public int PostId { get; set; }
public int CategoryId { get; set; }
public string PostImg { get; set; }
public virtual ICollection<Comment> Comments { get; set; }
//some other stuff
}
public class Category
{
public int CategoryId { get; set; }
public string CategoryName { get; set; }
public string Slug { get; set; }
public string Description { get; set; }
}
As you can see, the post has an int property representing its category. I would like my index-method to be able to take a parameter that can sort the posts by category depending on what is clicked in the view. If category had been a string property directly in my postclass, this would not have been a problem. But now that its only an int, i fail to see how to accomplish this.
Lets say I have a mthod that looks something like this:
public ActionResult Index(string category)
{
var model = new IndexViewModel
{
Posts = _repository.Posts.Where(o=>o.CategoryId == category).ToList(),
};
return View(model);
}
Of course the above does not work cos categoryId is an int and category is a string. I hope someone can see what im trying to do here.
Thanks!
You need to use join statement to get the post which has category with the given name. I assume that category in the parameter is the category name. Otherwise the answer given by Giannis is correct.
public ActionResult Index(string category)
{
var model = new IndexViewModel
{
Posts =_repository.Posts.Join(_repository.Categories.Where(c => c.CategoryName == category), p => p.CategoryId, c => c.CategoryId, (p, c) => p).ToList(),
};
return View(model);
}
You may either declare your argument as int:
public ActionResult Index(int category)
{
var model = new IndexViewModel
{
Posts = _repository.Posts.Where(o=>o.CategoryId == category).ToList(),
};
return View(model);
}
Or parse it into a variable:
public ActionResult Index(string category)
{
var categoryId = Int32.Parse(category);
var model = new IndexViewModel
{
Posts = _repository.Posts.Where(o=>o.CategoryId == categoryId).ToList(),
};
return View(model);
}

MVC An entity object cannot be referenced by multiple instances of IEntityChangeTracker

I've got a model that represents a joint table (with payload) in my database:
public class UserHasCar
{
// Foreign keys
[Key, Column(Order = 0)]
public string ApplicationUserId { get; set; }
[Key, Column(Order = 1)]
public int CarId { get; set; }
// Navigation properties
[Required]
public virtual ApplicationUser ApplicationUser { get; set; }
[Required]
public virtual Car Car{ get; set; }
// Additional fields
public int YearsRidden { get; set; }
}
public class Car
{
public int ID { get; set; }
public string Name { get; set; }
public virtual ICollection<UserHasCar> UserHasCars { get; set; }
}
public class ApplicationUser : IdentityUser
{
public int BirthYear{ get; set; }
public virtual ICollection<UserHasCar> UserHasCars { get; set; }
}
I have a form that includes multiple select boxes, and upon submitting I want to clear out all records related to that user who submitted the form in the UserHasCar table and replace them with the new updated information. I'm getting a An entity object cannot be referenced by multiple instances of IEntityChangeTracker. because I am doing something wrong, but I don't see where I am using more than one context. This code happens in my controller:
public ApplicationUser GetCurrentUser()
{
return UserManager.FindById(User.Identity.GetUserId());
}
public string GetUserId()
{
string id = User.Identity.GetUserId();
var user = UserManager.FindById(id);
return user.Id;
}
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult ManageCars(FormCollection form)
{
string id = GetUserId();
// Remove cars records with my id from database
var queryCars = (from m in db.UserHasCars where m.ApplicationUserId == id select m).ToList();
foreach (var record in queryCars )
{
// Could the problem be here?
db.UserHasCars.Remove(record)
}
// Add user-submitted cars to the database
string carval = form["Cars[0]"];
Car car = (from m in db.Cars where m.Name == carval select m).First();
int carid = car.ID;
// I get the abovementioned title error here
db.UserHasCars.Add(
new UserHasCar()
{
ApplicationUser = GetCurrentUser(),
ApplicationUserId = id,
Car = car,
CarId = carid,
YearsRidden = 0
}
);
db.SaveChanges();
}
I've seen many SO posts, but can't seem the problem as why my code doesn't want to save the new database entries.
EDIT
The solution was to remove the call to get the user and replace it with a query. Why? I was making database conflict errors by having both types of calls (database and DataManager calls in the same controller action). I ended up using a modified GetUser() function instead of GetCurrentUser()
Code:
public ApplicationUser GetUser()
{
// As opposed to:
// UserManager.FindById(User.Identity.GetUserId())
// We make a database call to grab our user instead
// So we don't get database context conflicts by using UserManager
string id = GetUserId();
return db.Users.Where(m => m.Id == id).First();
}
public string GetUserId()
{
return User.Identity.GetUserId();
}
// snip
// in ManageCars(FormCollection form)
ApplicationUser user = GetUser();
// snip
var newRow = db.UserHasCars.Create();
newRow.ApplicationUser = user;
// snip
db.UserHasCars.Add(newRow);
Try removing this line:
ApplicationUser = GetCurrentUser(),
from your object instantiation when adding.
Entity populates this object automatically once you set the foreign key ApplicationUserId. If UserManager.FindById(User.Identity.GetUserId()) uses a different db context that's where your exception is coming from.
Also to save yourself further trouble down the line, you should always call db.SaveChanges() in between the two operations. If you're worried about the atomicity of the db operation, just wrap the whole thing in a Transaction.
And when adding new rows to a table, I usually prefer to use something like:
var newRow = db.SomeTable.Create();
newRow.SomeColumn1 = "something";
newRow.SomeColumn2 = 5;
db.SomeTable.Add(newRow);
db.SaveChanges();
In order to delete entries from UserHasCars you need to change their EntityState to Deleted.
Example:
foreach (var record in queryCars )
{
db.ObjectStateManager.ChangeObjectState(record, EntityState.Deleted);
}
Hope this will fix your issue.

Best pratice for completing MVC model properties which aren't bound?

I am trying to find the best way to use MVC for models which are only partially edited.
Below is an simple example.
Model
using System.ComponentModel.DataAnnotations;
public class SimpleModel
{
public int Id { get; set; }
public string Parent { get; set; }
[Required]
public string Name { get; set; }
}
View
using System.ComponentModel.DataAnnotations;
public class SimpleModel
{
public int Id { get; set; }
public string Parent { get; set; }
[Required]
public string Name { get; set; }
}
Controller
using System.Web.Mvc;
public class SimpleController : Controller
{
public ActionResult Edit(int id)
{ return View(Get(id)); }
[HttpPost]
public ActionResult Edit(int id, SimpleModel model)
{
if (model.Name.StartsWith("Child")) //Some test that is not done client-side.
{
Save(model);
//Get the saved data freshly.
//model = Get(id);
}
else
{
ModelState.AddModelError("", "Name should start with 'Child'");
}
//Is this the way to set the Parent property?
//var savedModel = Get(id);
//model.Parent = savedModel.Parent;
return View(model);
}
//Mock a database.
SimpleModel savedModel;
private void Save(SimpleModel model)
{ savedModel = new SimpleModel() { Id = model.Id, Name = model.Name }; }
private SimpleModel Get(int id)
{
if (savedModel == null)
{ return new SimpleModel() { Id = id, Parent = "Father", Name = "Child " + id.ToString() }; }
else
{ return new SimpleModel() { Id = savedModel.Id, Parent = "Father", Name = savedModel.Name }; }
}
}
The Name field is editable. The Parent field is only for reference and should not be updated. Therefore, it is rendered using DisplayFor.
Upon post, I receive a model with property Parent set to null. That's no problem as it will not be saved. However, when I simply return the received model to the view, the Parent field will no longer be displayed. When the model is valid, I can easily get it again from the database and thus get the Parent field's value back.
When the model is not valid, I would like to allow the user to correct input and attempt to save once more. There, I the received model's values that are input should be used, but the displayed values should be shown as well.
In reality, there are many more fields to be shown for reference, most often from different database entities than the one that is being edited.
I have seen suggestions to pass the fields as hidden fields in the view, but I feel very reluctant to read data from the client that should not be updated.
Is there a more elegant way to do this than copying these values into the model manually or passing them as hidden fields?
What about giving those un-editable properties to another model and let it take care of those properties?
View Model
public class PersonViewModel
{
public int Id { get; set; }
[Required]
public string FirstName { get; set; }
[Required]
public string LastName { get; set; }
public PersonDetailsModel DetailsModel { get; set; }
}
Details Model
public class PersonDetailsModel
{
public string Mother { get; set; }
public string Father { get; set; }
public PersonDetailsModel() { }
public PersonDetailsModel(int personId)
{
// pull required model data from databases
var db = DBParentContext;
Mother = db.Parent.Where(m => m.ChildId == personId)
Father = db.Parent.Where(m => m.ChildId == personId)
}
}
Controller
public class PersonController : Controller
{
[HttpPost]
public ActionResult Edit(PersonViewModel viewModel)
{
viewModel.DetailsModel = new PersonDetailsModel(viewModel.Id)
if (ModelState.IsValid) {
// ~
}
return View(viewModel)
}
}
View
#model PersonViewModel
#Html.DisplayFor(m => m.DetailsModel.Mother)
#Html.DisplayFor(m => m.DetailsModel.Father)
#Html.TextBoxFor(m => m.FirstName)
#Html.TextBoxFor(m => m.LastName)
Since details like your Mother are un-editable then they're not really part of the "Edit" model, so I'd box like that away and try to let something else take care of them.
If you aren't going to update the Parent field, then it really doesn't matter if it's a hidden or not, since you won't update it on post.
I would use the hidden in this case, just make sure not to update that field.