ASP.NET MVC5 problem in passing data between Controllers - c#

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();
}

Related

Why I obtain this "InvalidOperationException" when I try to render this .NET view?

I am pretty new in .NET and C# (I came from Java and Spring framework) and I have some problem following a tutorial.
I have this simple controller class:
namespace Vidly.Controllers
{
public class CustomersController : Controller
{
public ViewResult Index()
{
var customers = GetCustomers();
return View(customers);
}
public ActionResult Details(int id)
{
System.Diagnostics.Debug.WriteLine("Into Details()");
var customer = GetCustomers().SingleOrDefault(c => c.Id == id);
System.Diagnostics.Debug.WriteLine("customer: " + customer.Id + " " + customer.Name);
if (customer == null)
return HttpNotFound();
return View(customer);
}
private IEnumerable<Customer> GetCustomers()
{
return new List<Customer>
{
new Customer { Id = 1, Name = "John Smith" },
new Customer { Id = 2, Name = "Mary Williams" }
};
}
}
}
As you can see this class contains this Details(int id) method:
public ActionResult Details(int id)
{
System.Diagnostics.Debug.WriteLine("Into Details()");
var customer = GetCustomers().SingleOrDefault(c => c.Id == id);
System.Diagnostics.Debug.WriteLine("customer: " + customer.Id + " " + customer.Name);
if (customer == null)
return HttpNotFound();
return View(customer);
}
So, this method handle HTTP request of type GET toward URL like:
localhost:62144/Customers/Details/1
and it seems to work because into the output console I obtain the Into Details() log. Also the other log explains that the customer model object is correctly initialized, infact I obtain this console output:
customer: 1 John Smith
Then the controller reuturn a ViewResult object (calling the View method) containinf the previous model object.
I think that .NET automatically try to send this ViewResult object (that contains the model) to a view having the same name of the controller method that handle this request. So I have this Details.cshtml view:
#model Vidly.Models.Customer
#{
ViewBag.Title = Model.Name;
Layout = "~/Views/Shared/_Layout.cshtml";
}
<h2>#Model.Name</h2>
that in theory should receive this ViewResult object, from here extract the model object (having Vidly.Models.Customer as type) and it should print the value of the Name property of this model object.
The problem is that I am obtaining this excepetion istead the expected page with the expected data:
[InvalidOperationException: The model item passed into the dictionary is of type 'Vidly.Models.Customer', but this dictionary requires a model item of type 'Vidly.ViewModels.RandomMovieViewModel'.]
Why? What it means?
Vidly.ViewModels.RandomMovieViewModel is another model object used into another controller and another view.
What is the problem? What am I missing? How can I fix this issue?
This error was appearing due to Vidly.ViewModels.RandomMovieViewModel model declaration in _Layout.cshtml file.
Declaring a model inside layout view means that all views that are using layout view must either use that model class or a class that derives from that layout view model class

Transfering data between action methods ASP.NET

I have the following action methods:
[HttpGet]
public ActionResult DBLookupIndex(DBLookupDTO dto)
{
dto.Areas = _ph.GetProfiles();
return View(dto);
}
[HttpGet]
public ActionResult Search(DBLookupDTO dto)
{
dto.Orders = _oh.GetOrders(dto.OrderNumber, dto.ProductNumber, dto.DateRange, dto.SelectDeleted, dto.AreaId);
return RedirectToAction("DBLookupIndex", dto);
}
The user simple enters valid information into one or more of the textboxes on the webpage, and submits it to the controller by pressing submit. This then calls the Search-action.
By debugging, I have determined, that the function works. It does find what it should, but it is not passed on, when it redirects back to the DBLookupIndex-action.
My question is; What am I doing wrong? I have seen code examples similar to the one above provided as solutions for similar issues, but for some reason it does not work for me.
EDIT:
I realised after the first answer came, that I was missing some information. Whenever the page is loaded, it has to update a dropdown that is rendered in the view, in case new profiles/areas have been added. I do that with razor:
<select name="AreaId" asp-for="AreaId" class="form-control">
<option disabled selected value=""> -- Vælg et område -- </option>
#foreach (var a in Model.Areas)
{
<option value="#a.ProfileId">#a.Name</option>
}
That is why I have to used RedirectToAction, instead of having a new action render the same view. Unless there is a better way to do it? :)
Thank in advance for any help!
In addition to Peter B's answer, another option is to store it in the TempData object that exists on your base controller.
[HttpGet]
public ActionResult Search(DBLookupDTO dto)
{
var orders = new List<Order>();
TempData["orders"] = orders;
return RedirectToAction("DBLookupIndex", dto);
}
You can then retrieve the data on the following request like so:
[HttpGet]
public ActionResult DBLookupIndex(DBLookupDTO dto)
{
var yourData = (List<Order>)TempData["orders"];
...
return View(dto);
}
The TempData object exists for a single request and is then cleared up. You can read more about it here.
The object parameter in RedirectToAction is meant to set querystring values for the URL that is generated and then sent to the browser as a "Redirect to other page" result. It is supposed to be an object similar to new { id = 7, otherParam = 5 }, or a Dictionary, but certainly not a recordset or any other kind of business data.
It seems like you want to show the View that belongs to the DBLookupIndex action. That can be done in a very simple way, like this:
[HttpGet]
public ActionResult Search(DBLookupDTO dto)
{
dto.Orders = _oh.GetOrders(dto.OrderNumber, dto.ProductNumber, dto.DateRange, dto.SelectDeleted, dto.AreaId);
return View("DBLookupIndex", dto); // Render the "DBLookupIndex" view and pass it the dto object
}
Update: if you need dto.Areas to be always set, you can create a method that just does that.
Like this (1):
[HttpGet]
public ActionResult DBLookupIndex(DBLookupDTO dto)
{
SetAreas(dto);
return View(dto);
}
[HttpGet]
public ActionResult Search(DBLookupDTO dto)
{
dto.Orders = _oh.GetOrders(dto.OrderNumber, dto.ProductNumber, dto.DateRange, dto.SelectDeleted, dto.AreaId);
SetAreas(dto);
return View("DBLookupIndex", dto);
}
private void SetAreas(DBLookupDTO dto)
{
dto.Areas = _ph.GetProfiles();
}
Or like this (2):
[HttpGet]
public ActionResult DBLookupIndex(DBLookupDTO dto)
{
return SetAreasAndView(dto);
}
[HttpGet]
public ActionResult Search(DBLookupDTO dto)
{
dto.Orders = _oh.GetOrders(dto.OrderNumber, dto.ProductNumber, dto.DateRange, dto.SelectDeleted, dto.AreaId);
return SetAreasAndView(dto);
}
private ActionResult SetAreasAndView(DBLookupDTO dto)
{
dto.Areas = _ph.GetProfiles();
return View("DBLookupIndex", dto);
}

Unable to retrieve querystring data

In my bus service MVC project I need to be able to display a bus route stops only when i go to the route table. I have this ActionLink sending the querystring and the page to the route table: #Html.ActionLink("Route Stops", "index", "snRouteStops", new { id=item.busRouteCode}, null)
But when I get to my bus route stops controller I need it to throw me back to the route lists if there isn't one selected. This part works fine but when I click a route to view the stops nothing happens. Here is my code in the actionResult of the busRouteStops controller:
public ActionResult Index()
{
string busRouteCode = "";
if (string.IsNullOrWhiteSpace(Request.QueryString["busRouteCode"]))
{
if (Request.Cookies["busRouteCode"] == null)
{
return RedirectToAction("index", "snBusRoutes");
}
else
{
busRouteCode = Request.Cookies["busRouteCode"].ToString();
}
}
else
{
busRouteCode = Request.QueryString["busRouteCode"].ToString();
}
var routeStops = db.routeStops.Include(r => r.busRoute).Where(r => r.busRouteCode == busRouteCode).Include(r => r.busStop);
return View(routeStops.ToList());
}
}
Your main issue is that you are looking for a query string with the name busRouteCode, but your action link is not setup to provide a query string with that name. Your action link -
#Html.ActionLink("Route Stops", "index", "snRouteStops", new { id=item.busRouteCode}, null)
is configured to send the busRouteCode as a parameter with the name id. That means that the URL will look like this:
/snRouteStops/Index?id=myBusRouteCode
Thus, there is no query string with the name busRouteCode
You can do a few things to clean this up.
Standard routing in MVC is configured to use /Controller/Action/id. You can update your index action to be aware that an Id is coming by updating the action method.
//appears id is a string based on your code
public ActionResult Index(string id)
{
string busRouteCode = "";
if(string.IsNullOrEmpty(id))
{
if (Request.Cookies["busRouteCode"] == null)
{
return RedirectToAction("index", "snBusRoutes");
}
else
{
busRouteCode = Request.Cookies["busRouteCode"].ToString();
}
}
else
{
busRouteCode = id;
}
var routeStops = db.routeStops.Include(r => r.busRoute).Where(r => r.busRouteCode == busRouteCode).Include(r => r.busStop);
return View(routeStops.ToList());
}
}
Change your action link to send the busRouteCode as a parameter named busRouteCode.
#Html.ActionLink("Route Stops", "index", "snRouteStops", new { busRouteCode=item.busRouteCode}, null)
This will generate a link that looks like:
/snRouteStops/Index?busRouteCode=myBusRouteCodeValue
However you proceed, you should not really ever have to use Request.QueryString[] in your MVC controller actions. By configuring your action method to be looking for those query string values(like I did in example one), you can now get strongly typed parameters in your action methods as opposed to the string values in the QueryString[] dictionary. This is especially helpful when expecting an integer as a query string value for instance. The model binder will take care of ensuring that query string value is actually an integer and not a string.
This code {id=item.busRouteCode} generates query like that:
http://snRouteStops/Index?id=somedata
where somedata = item.busRouteCode.
Try to look for id in query string or just add id to action params, like that:
public ActionResult Index(string id)

Passing newly created object to an another page

I'm working on a mvc4 project and I cant seem to figure out how to pass a newly created object from one page to another page.
on page A
im submitting a form to the database which is creating a new object in placing it into the table.
but after the form is submitted I want a confirmation page to appear afterwards. I dont know how to pull the newly created object from the previous page that saved it to the database. I could call it with the Id that was made but I dont know how to call it without knowing the id
public ActionResult Pay(int id,Paid paid)
{
PaidAdapter cAdapter = new PaidAdapter();
paid.CId = id;
if (ModelState.IsValid)
{
cAdapter.StorePaid(paid);
return RedirectToAction("SubmitPayment");
}
return View(paid);
}
public ActionResult SubmitPayment(int id)
{
var cAdapter = new PaidAdapter();
var model = cAdapter.GetPaidViewModel(id);
return View(model);
}
public Paid StorePaid (Paid paid)
FContext db = new FContext();
paid= db.Paid.Add(paid);
db.SaveChanges();
//return the paidId
return paid;
}
public PaidViewModel GetPaidViewModel(int id)
{
var model = new PaidViewModel();
FContext db = new FContext();
model.Paid= db.Paid.Where(c => c.PaidId == id).FirstOrDefault();
return model;
}
Use the overload of RedirectToAction that accepts route parameters. Assuming you're using the default route and it has an id parameter the code would be:
return RedirectToAction("SubmitPayment", new { id = paid.PaidId });

Sorting results that have been posted by another view

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

Categories