I have read many questions and tutorials about this, I couldn't find a case similar to mine.
I have an index view which has a search form inside to filter results available in my database.
Here's the simplified version my controller action for my index view:
[HttpPost]
public ActionResult Index(String carMake, String carModel)
{
var cars = from d in db.Cars
select d;
if (!String.IsNullOrEmpty(carMake))
{
if (!carMake.Equals("All Makes"))
{
cars = cars.Where(x => x.Make == carMake);
}
}
if (!String.IsNullOrEmpty(carModel))
{
if (!carModel.Equals("All Models"))
{
cars = cars.Where(x => x.Model == carModel);
}
}
cars = cars.OrderBy(x => x.Make);
return View("SearchResult", cars);
}
as you can see, I get the data from Index view and post it to my SearchResult view directly without the use of any public ActionResult SearchResult(){...}. But I do have public ActionResult SearchResult(){...} method in my controller.
Now, I'm trying to sort my results. How can I do that?
I have tried many different ways to pass data from my Index method to Search Result method such as TempData and so on. But the problem with them is when I click on the sort button the page reloads and the Query that I have taken from my Index view form will be all null because TempData is only useful for passing data among methods and when the method is recalled, the data will be gone and all null.
I have some solutions in my mind but I'm not sure if they will work out and if they're the best/easiest solution possible to come up with. Which is recording the Query I get from my Index page into a table (I don't think would be a wise thing to do tho).
I think it should be as;
[HttpPost]
public ActionResult Index(String carMake, String carModel)
{
//Redirect to SearchResults. You can do this from client as well.
return RedirectToAction("SearchResult",
new { make = carMake, model = carModel });
}
//Add your filter and order code here
public ActionResult SearchResult(String make, String model)
{
var cars = from d in db.Cars
select d;
if (!String.IsNullOrEmpty(make))
{
if (!carMake.Equals("All Makes"))
{
cars = cars.Where(x => x.Make == make);
}
}
if (!String.IsNullOrEmpty(model))
{
if (!carModel.Equals("All Models"))
{
cars = cars.Where(x => x.Model == model);
}
}
cars = cars.OrderBy(x => x.Make);
return View(cars);
}
Instead of returning to views why don't you directly call Search result action in your controller.
Another way is, if you mandatorily have to call Index action, after your process
use
return SearchResult(cars);
instead of
View("SearchResult", cars);
Regards,
Pavan.G
Related
I am building a basic Car Rental Application. The user can view the cars and click the Rent button. After clicking it, I need to return a new View which contains a form, that the user has to complete in order to finish the order. I am having problems passing the Car data as well as the Customer data between the controllers in order to complete the Rent.
On the main page, I have a Rent link under every car. Here is the code:
<div class="col-md-12">
<p>#Html.ActionLink("Rent", "Rent" , new { Id = car.Id})</p>
</div>
Rent method from HomeController
public ActionResult Rent(string id)
{
return RedirectToAction("Create", "Rents");
}
Create method from RentsController
[HttpPost]
public ActionResult Create(string carId, Rent rent)
{
if (!ModelState.IsValid)
return View();
var carToRent = context.Cars.SingleOrDefault(c => c.Id == carId);
if (carToRent == null)
return Content($"Car not found!");
rent.Car = carToRent;
var customer = context.Customers.SingleOrDefault(c => c.UserId == User.Identity.Name);
if (customer == null)
return Content($"Customer not found!");
rent.Customer = customer;
context.Rents.Add(rent);
context.SaveChanges();
return RedirectToAction("Index");
}
I am getting an HTTP 404 Error every time I try to access Rents/Create.
You can simplify what you're attempting to do. Main points to note are the following:
You don't need to link to the Rent action if all it does is
redirect to the Create action- just link to the Create action
directly. There is another overload of ActionLink that will let you specify
the controller (see below).
From what you've posted it doesn't look like the Create action
needs to take in a parameter for Rent rent- this can be created
inside the Create action and simplify the data that you need to
pass from view to controller.
Please see my comments in code for further explanantion:
View:
//call the Create action on the RentsController directly from the view
<div class="col-md-12">
<p>#Html.ActionLink("Rent", "Create", "Rents" , new { Id = car.Id }, null)</p>
</div>
Controller:
//modify signature to remove passing a Rent object it
//you can create this object inside of this method
//and do not need to pass one in so remove it from the method signature
[HttpPost]
public ActionResult Create(string carId)
{
if (!ModelState.IsValid)
return View();
var carToRent = context.Cars.SingleOrDefault(c => c.Id == carId);
if (carToRent == null)
return Content($"Car not found!");
var rent = new Rent(); //this line has been added since the method signature was changed
rent.Car = carToRent;
var customer = context.Customers.SingleOrDefault(c => c.UserId == User.Identity.Name);
if (customer == null)
return Content($"Customer not found!");
rent.Customer = customer;
context.Rents.Add(rent);
context.SaveChanges();
return RedirectToAction("Index");
}
and finally you can remove the following:
//delete this action entirely, if youre doing nothing other than redirecting
//to an action then just link directly to the action you want
//notice the ActionLink in the view is modified to hit the Create action directly
public ActionResult Rent(string id)
{
return RedirectToAction("Create", "Rents");
}
As you can see below you can pass parameters in RedirectToAction() method.
RedirectToAction(String, String, RouteValueDictionary)
Redirects to the specified action using the action name, controller name, and route values. Try to redirect Create action with the carId and Rent object.
I dont know using multiple post object, but you can post one post object like that
public class MyPostObject
{
public string carId { get; set; }
public Rent rent{ get; set; }
}
and post it like that
[HttpPost]
public ActionResult Create(MyPostObject myPostObject)
{
string carId=myPostObject.carId;
Rent rent = myPostObject.rent;
....
}
UPDATE : Or you can use multiple post object with Ajax
$("#btnSave").on('click', function () {
var url = '#Url.Action("Create", "Rent")';
//Rent class properties
var data=
{
Brand: 'Renault',
Model: 'Megan',
};
$.ajax({
url:url,
type:"POST",
data:{
carId:'12',
rent:data
},
datatype:'json',
ContentType:'application/json;utf-8'
}).done(function(resp){
alert('Success ' +resp);
}).error(function(err){
alert("Error " + err.status);
});
});
As mentioned in the comments, you will have to pass the required parameters into the redirect statement.
public ActionResult Rent(string id)
{
Rent rentItem = new Rent();
return RedirectToAction("Create", "Rents", new { carId = id, rent = rentItem});
}
You either have not passed the parameters or you are missing the below method if you are looking to return a view with your redirect
public ActionResult Create()
{
return View();
}
I have created a database and then an MVC solution using VisualStudio Exrpess 2013 for WEB and
I created a ViewModel to display an Edit view with dropdown lists.
The view model includes just the property 'parlists' of type PartnerList, which is the model representing the main table of the database, and 2 properties of type SelectList which I use to create the dropdown lists in the view.
The code of the viewmodel is as follows:
public class FileStatusEdit
{
public SelectList HoldingsStatus { get; set; }
public SelectList RealGainStatus { get; set; }
public PartnerList parlists { get; set; }
}
In the controller I have the following code for the HttpGet edit method:
public ActionResult Edit(int? id)
{
if (id == null)
{
return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
}
var viewModel = new FileStatusEdit
{
HoldingsStatus = new SelectList(db.Statuses, "Status_ID", "Status1", db.PartnerLists.Where(p => p.IntermediaryID == id).Single().AssetDataSource.HoldingsFile.Status_ID),
RealGainStatus = new SelectList(db.Statuses, "Status_ID", "Status1", db.PartnerLists.Where(p => p.IntermediaryID == id).Single().AssetDataSource.RealGainFile.Status_ID),
parlists = db.PartnerLists
.Include(p => p.AssetDataSource)
.Where(p => p.IntermediaryID == id)
.Single()
};
if (viewModel.parlists == null)
{
return HttpNotFound();
}
return View(viewModel);
}
This code is working fine and the view is correctly displaying a form with the dropdown lists. I omit the view code as it is quite long and propably not relevant.
So far so good.
My Http Post Edit method, however, is not saving the changes to the database. The code is as follows:
[HttpPost, ActionName("Edit")]
[ValidateAntiForgeryToken]
public ActionResult EditPost(FileStatusEdit newParList)
{
if (TryUpdateModel(newParList.parlists, "",
new string[] { "Firstname", "Surname", "Category", "ClientID", "IntermediaryID", "ExternalRef", "RecordStatus", "Asset_Data_Source_ID", "New_Communication_Issued", "AssetDataSource", "HoldingsFile", "RealGainFile"}))
{
try
{
db.Entry(newParList.parlists).State = EntityState.Modified;
db.SaveChanges();
return RedirectToAction("Index");
}
catch (RetryLimitExceededException)
{
ModelState.AddModelError("", "Unable to save changes.");
}
}
return View(newParList);
}
As you can see I am passing the viewmodel (newParList) to the EditPost method and then I update it using TryUpdateModel. By stepping into the debugging process I can see that the database record newParList.parlists is correctly updated with the user input, however when the step db.SaveChanges() is executed the program redirects to the Index view without saving the changes to the database.
I tried using Attach as suggested in some posts but I believe the attach step is already included in the line 'db.Entry(newParList.parlists).State = EntityState.Modified;' and that did not indeed solve the problem.
I examined a lot of posts and tried different solutions but none of them worked so I would appreciate some help.
I suspect you're missing the context add or update.
Here's an example of how I handle the creation of a new record.
For an update you would find the record first then save the changes.
public void SaveCreatedMessage(Message message)
{
var dbEntry = _context.Message.Add(message);
if (dbEntry != null)
{
// Create the new record
dbEntry.CustomerID = message.CustomerID;
dbEntry.MessageID = message.MessageID;
dbEntry.Description = message.Description;
dbEntry.Text = message.Text;
dbEntry.IsRead = message.IsRead;
dbEntry.CreatedOn = message.CreatedOn;
dbEntry.CreatedBy = message.CreatedBy;
_context.Message.Add(message);
}
_context.SaveChanges();
}
I think I found the solution. I was not updating the correct entity.
In my HttpPost Edit method I now replaced the following line:
db.Entry(newParList.parlists).State = EntityState.Modified;
with:
db.Entry(newParList.parlists.AssetDataSource.HoldingsFile).State = EntityState.Modified;
db.Entry(newParList.parlists.AssetDataSource.RealGainFile).State = EntityState.Modified;
Now my entities HoldingsFile and RealGainFile are updated after SaveChages() is executed.
Warning: This is my first web app.
I have 4 models, views and controllers. Lets call them A, B, C, D(ex. ModelA, ControllerA, ViewA). They are all basic views with list scaffolding.
/ControllerA/Index
User starts at ViewA and Selects an the first item, which redirects the user to ViewB
/ControllerB/Function?Aid=1
ViewB shows another list based on Selection from ViewA. Then the user Selects again is is redirected to ViewC
/ControllerC/Function?Aid=1&Bid=2
ViewC shows another list based on Selections from ViewA and ViewB. Then the user Selects again is is redirected to ViewD.
/ControllerD/Function?Aid=1&Bid=2&Cid=3
ViewD shows another list based on Selections from ViewA, ViewB, and ViewC, Then the user Selects again.
At this point I would like to POST Aid, Bid, Cid, and Did and save them in my database. Ideally the user would click the link, the data would be posted and then the site would redirect the user back to the homepage. Should I create another model and controller to Handle the post? I thought about trying to do the POST from controllerD but that doesn't seem like the proper way to do this.
The msdn tutorials only show posting directly from a view with a strongly typed model. I kinda stuck and I would prefer not to make this a complete mess.
Edit for Code
Controller
public ActionResult myFunction(int Aid = 0, int Bid, int Cid)
{
//query D stuff here
if (D == null)
{
return HttpNotFound();
}
return View(D.ToList());
}
[HttpPost]
[InitializeSimpleMembership]
public ActionResult CreateQuote(int Aid, int Bid, int Cid, int Did)
{
Quote myQuote = new Quote();
myQuote.Customer_ID_FK = (int)Membership.GetUser().ProviderUserKey;
myQuote.A_ID_FK = Aid;
myQuote.B_ID_FK = Bid;
myQuote.C_ID_FK = Cid;
myQuote.D_ID_FK = Did;
if (ModelState.IsValid)
{
db.Quotes.Add(myQuote);
db.SaveChanges();
db.Quotes.Max();
int mymax = db.Quotes.Max(q => q.ID);
return RedirectToAction();
}
return View(D.ToList());
}
[HttpPost]
[InitializeSimpleMembership]
public ActionResult CreateQuote(Quote myQuote)
{
myQuote.Customer_ID_FK = (int)Membership.GetUser().ProviderUserKey;
if (ModelState.IsValid)
{
db.Quotes.Max();
int mymax = db.Quotes.Max(q => q.ID);
db.Quotes.Add(myQuote);
db.SaveChanges();
return RedirectToAction();
}
return View(D.ToList());
}
It usually makes sense to put your post handler in the controller it's related to. This isn't always the case, as sometimes it would make more sense to make a new controller to handle all posts related to a certain task. You should also understand the distinction between a method IN a controller, and a controller. A controller is just a class that inherits from System.Web.Mvc.Controller and can have methods just like any other class. A perfectly reasonable controller could look like this:
public class DController : Controller
{
//GET /d
public ActionResult Index()
{
//MyModel is a class that would contain the logic to display
//the selections from A, B, and C
var model = new MyModel();
return View(model);
}
//POST /d/saveresults
//We only want this method to accept POST
[HttpPost]
public ActionResult SaveResults(MyEntity data)
{
var model = new MyModel();
model.SaveResultsToDatabase(data);
return Redirect("/");
}
}
The important thing in a controller is to keep logical processing to a minimum. There's nothing wrong with having an if statement here and there, but the majority of your logic should be handled by your model. A controller is there primarily to pass data between your views and models.
I'm just getting into MVC properly, and I'm stuck on a concept which I figured would be relatively simple.
So, I have a form, in which posts to a Controller (which is fine), at the minute I have:
Controller
public ActionResult TestAction(int productId)
{
// What to do here... ?
return View();
}
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult TestAction(FormCollection values)
{
// Do work here and return the objects
return View(Products);
}
View
#foreach (var product in Model)
{
<tr>
<td>#product.Provider.Name</td>
<td>£#product.MonthlyPremium</td>
<td>#Html.ActionLink("More Details", "TestAction", new { productId = product.ProductId })</td>
<td>Compare</td>
</tr>
}
// I want the singular product details to be displayed here, under the product list... but how?!
Now, what I want is when you click on "More Details" (the ActionLink) then the product details are going to be displayed, which are part of the singular Product objects. I got the TestAction controller being called from GET, but how do I retain the view of the products and display the details of the singular product? Assign this singular Product to the ViewBag and do it that way? Then, for the list of products, cache the original list and use that cache?
I want this done via postbacks as this is for the non-JS version of my site.
Surely there has to be a better way of doing this, or have I been babied with ViewState for too long?
You could add a property to your model, for example bool ViewDetail, and set that in your controller for the item that corresponds to the productId parameter:
public ActionResult TestAction(int productId)
{
// TODO: error checking
Products.Single(m => m.ProductId == productId).ViewDetail = true;
return View(Products);
}
And display it on your view:
var productDetail = Model.SingleOrDefault(m => m.ViewDetail == true);
if (productDetail != null)
{
// Display product details
}
Or you could alter your model to contain:
public class ProductsWithDetailModel
{
public IEnumerable<Product> Products { get; set; } // to loop over and display all products
public Product DetailProduct { get; set; } // to display product details, if not null
}
Then again, set the DetailProduct based on the productId parameter, and display it in the view if it is not null.
var s = myInventoryEntities.p_Forms().Select(i => new { i.Customer, i.Order_ID }).Distinct().ToList();
ViewBag.MyData = s;
return View();
this gives me
{ Customer = "Bob", Order_ID = 2644550 }
In my razor code
i traverse the data
#foreach ( var x in #ViewBag.MyData){
x.Customer // Does not exist! :(
}
Please help!
'object' does not contain a definition for 'Customer'
ViewBag data's life time is very limited. Are you sure you are coming to this view from the same action method where you set the ViewBag data ?
Suggestion : Try to Avoid Dynamic types liek ViewData/ViewBag. Sticking to Strongly typed makes your code much better readable/ maintainable.
If your Domain object and What you want to display is same, do not bother about creating a ViewModel. else create a viewmodel
public class CustomerOrder
{
public string CustomerName
public int OrderID { set;get;}
//other properties as relevant (to the view)
}
and return that instead of the ViewBag
public ActionResult GetOrders()
{
var s = myInventoryEntities.p_Forms().
Select(i => new CustomerOrder {
CustomerName= i.Customer,
OrderID=i.Order_ID
}).Distinct().ToList();
return View(s);
}
And in the view
#model IList<CustomerOrder>
#foreach ( var x in Model)
{
<p>#x.Customer</p>
}