Passing a View Model within a URL.Action - c#

I'm trying to pass a view model to my controller.
#if (User.IsInRole("Customer"))
{
<input type="button" class="btn btn-danger" value="Rent Car" onclick="location.href='#Url.Action("PassingCar", "Bookings", new { id = item.VehicleID, Model = Model.Booking })'" />
}
I'm using a dynamic model so I can use both Vehicle and Booking in this view.
When the code gets to my controller the ID has been passed over but the data in the ViewModel is gone.
public ActionResult PassingCar( int id, CreateBookingViewModel createdModel)
{
///Checks that Vehicle exists in DB and v property is not null
if (v == null)
{
return HttpNotFound();
}
else
{
/// sets the Vehicle attribute of the BookingViewModel to vehicle passed over
createdModel.Vehicle = v;
}
return RedirectToAction("Create", "Bookings");
}
If anybody has an idea what i'm doing wrong it would be greatly appreciated.

Can you post the text of the URL you end up at?
But at a guess, you might want to replace Model = Model.Booking with Model = JSON.Encode(Model.Booking)
Oh. And another probability. You name the parameter "Model" in the Url Action, but "createdModel" in the method signature.

I discoverer my problem so I'm gonna post an answer for anyone encountering the same thing, and find this thread.
Because both of the names in the URL Action where called Model, this would create a brand new ViewModel passed to the view. This was due to the fact in my View, the model was a dynamic model I created so the Object that was being created was anew ExpandoObject.
A solution would of been to cast the ExpandoObject to the correct type, but I discovered a different way to solve my specific problem just using TempData. Either way would of worked.

Related

Asp.net MVC & AJAX

I have an Ajax button that whenever I click it, it shows a single record from the database (in my Controller I used .Take(1) )
public PartialViewResult BtnNext()
{
List<Queue> model = db.Queues.OrderBy(x => x.QueueNumber).Take(1).ToList();
return PartialView("_queuenumber", model);
}
What I would like to do here is - whenever I click the next button it will display the first record from the database, then when I click it again it will show the second record and so on..
I wonder if that is even possible and what kind of stuff should I use to do that?
Yes. Its possible.
Just set Application["counter"] = 0 in Application_Start function then make value increments by 1 in result view and use it to get next record.
public PartialViewResult BtnNext()
{
List<Queue> model = db.Queues.OrderBy(x => x.QueueNumber).Skip(Application["counter"]).Take(1).ToList();
Application["counter"] = Application["counter"] + 1;
return PartialView("_queuenumber", model);
}
Reference
Use FormCollection try following code.
public PartialViewResult BtnNext(FormCollection Form)
{
Int32? Count = Convert.ToInt32(Form["Count"]??0);
List<Queue> model = db.Queues.OrderBy(x => x.QueueNumber).ToList();
model.ElementAt(count); // [NotMapped] public Int32? count { get; set; } add in model class
model.count=count+1;
return PartialView("_queuenumber", model);
}
on view
<input type="submit" class="btn btn-primary" value="Save" id="BtnNext">
<input type="hidden" id="Count" name="Count" value="#Model.Count" />
A good practice when you realize your Views need to handle and manipulate your data, is to create a ViewModel class that wraps all the objects that you need to send to that view. In your case, you can start with a simple
public class QueueViewModel
{
public Queue Queue { get; set ; }
public int CurrentRecord { get; set ; }
}
Now, all you have to do is changing the action method the controller so that you initialize and pass the ViewModel to the View. It will also be better to have an optional argument acting as the default record, and then using the linq instruction Skip to go to and take a specific record:
Public PartialViewResult NextRecord(int current = 0)
{
QueueViewModel model = new QueueViewModel();
model.CurrentRecord = current;
model.Queue = db.OrderBy(x => yourClause).Skip(current).Take(1);
return PartialView(“yourview”, model);
}
I changed the List<Queue> within your model as I think you don’t need a list if you’re only showing one record at a time, but you can easily go back to the generics if you feel you really need to.
As for the view part where you handle the index on the model, there are many ways to achieve the same result. What I personally like to do is using the model to fill a data attribute of a DOM element and use that in the Ajax call. Since you now have
#model yourModelNamespace.QueueViewModel
it is possible for you to set an element (let’s say a button) to host the current value:
<button data-current-record=“#Model.CurrentRecord”>...</button>
You can now very easily retrieve that value within your Ajax call to the action method:
var currentRecord = parseInt($(‘button’).data()[currentRecord]);
$.ajax({
url: yourPathToTheAction,
type: ‘GET’,
data: {
current: currentRecord + 1
}
});
This way you can go further and add other functions calling the same controller to move to previous record or jump to the last or the first and so on...

Efficiently passing large number of parameters to ASP.NET MVC Controller Action

I'm trying to determine a more efficient way to pass a large number of parameters to my controller action. I tried to look at similar questions, but they didn't seem to offer any real explanation past what I have already implemented.
As an example, I have a simple generated CRUD program that implements the PagedList package. This CRUD program needs to have multiple filters (10+). Previously, I have been passing the parameters through the URL.
Simple example:
// GET: Action/rows
[HttpGet]
public async Task<ActionResult> Index(int? page, string currentrowId, string rowId)
{
if (rowId != null)
{
page = 1;
}
else
{
rowId = currentRowId;
}
var data = from s in db.tblrows
where s.rowId.ToString().Contains(rowId)
select s;
int pageSize = 10;
int pageNumber = (page ?? 1);
ViewBag.Page = page;
ViewBag.currentrowId = rowId;
return View(await data.ToPagedListAsync(pageNumber, pageSize));
}
Then, in my view, I maintain my parameters by passing them through the URLs in each CRUD view. For example, in my index view I can open an item in the edit view using the following:
#Html.ActionLink("Edit", "Edit", new { id = item.rowId, page = ViewBag.Page, currentrowId = ViewBag.currentrowId }, new { #class = "btn btn-success btn-sm" })
In the edit view, I have similar code that maintains the current parameter so that when the user returns to the CRUD interface, their parameters are intact.
This way works fine for a few parameters, but it seems extremely tedious for many parameters. I considered creating a model for my search parameters and passing it as part of my ViewModel, but this didn't seem very efficient either when considering what that would require.
Any documentation or suggestions on a better way would be appreciated.
EDIT:
Since this is MVC and I am using a GET action method, I cannot pass an object to the method.
You can pass objects to MVC actions using HttpGet....here is an example from live code we have in our solution....I changed the objects and removed our implementation, but it is definitely possible. The [FromUri] is what tells the model binder to work with complex objects in get requests.
[HttpGet]
[Route("orderitems")]
public DataResponse<List<ItemDTO>> GetItems([FromUri]SearchObject search)
{
// Do stuff
}
You can pass an object as parameter. It's a technique used when you have a large number of parameters.
See more details here:
https://www.includehelp.com/dot-net/how-to-pass-object-as-argument-into-method-in-c-sharp.aspx

How to create server method, that populates collection and returns partial view, in ASP.NET MVC?

I am trying to call IEnumerable method in my _Layout.cshtml file. At the final I was adviced to "use html.action - to call server method that populates collection and returns partial view".
Currently I have created partial file _Dodatki.cshtml, that contains call of IEnumerable method (Aktualnosci.cs is model file):
#model IEnumerable<DluzynaSzkola.Models.Aktualnosci>
In my _Layout.cshtml I called method from my constructor with:
#Html.Action("_Dodatki", "AktualnosciController ", new {area="" })
And at the final I want to create method in my AktualnosciConstructor.cs file. Currenly I have method:
[ChildActionOnly]
[ActionName("_Dodatki")]
public ActionResult Dodatki()
{
IList<Aktualnosci> lista = new IList<Aktualnosci>();
return PartialView("_Dodatki", lista);
}
Unfortunately, when using syntax as above, it gives me message in compiler:
"cannot create an instance of the abstract class or interface
'IList'".
When replacing 'IList' with 'List', it gives me exception:
"System.Web.HttpException: The controller for path '/' was not found
or does not implement IController."
I have no idea how in other way I can populate collection in the method.
edit: As per request, below AktualnosciController.cs definition, with no other methods:
namespace DluzynaSzkola.Controllers
{
public class AktualnosciController : Controller
{
//here are other methods
[ChildActionOnly]
[ActionName("_Dodatki")]
public ActionResult Dodatki()
{
IList<Aktualnosci> lista = new IList<Aktualnosci>();
return PartialView("_Dodatki", lista);
}
}
}
as noticed by GTown-Coder your controller name seems wrong. Updated my answer accordingly.
I think that your problem might be the same as answered by this SO post.
try specifying the Area name and, if this controller is not in an area simply add an empty area name.
#Html.Action("_Dodatki", "AktualnosciController ", new {area="" })
Even if this does not solve your problem it is good practice because if this view is latter used within an area it will try to find the controller in the area and not in the root space.
Allright, I have implemented changes to my project, that works fine.
My in _Layout.cshtml call is changed a bit. AktualnosciController supposed to be called just Aktualnosci !!!
<div class="kontenerDodatkiLayout hidden-xs hidden-sm">
<div class="archiwum">Archiwum</div>
#Html.Action("_Dodatki", "Aktualnosci", new { area = "" })
</div>
My partial view _Dodatki.cshtml model call is changed a bit:
#model IEnumerable<DateTime>
<div class="wpisDodatki">
#foreach (var item in Model)
{
<div> #Html.DisplayFor(modelItem => item)</div>
}
<p>test<br />test<br />test</p>
</div>
And method in my controller AktualnosciController.cs looks like that:
//[ChildActionOnly]
[ActionName("_Dodatki")]
public ActionResult Dodatki()
{
using (var context = new DluzynaContext())
{
var lista = context.Indeks.Select(it => it.Dzien).ToList();
return PartialView("_Dodatki", lista);
}
}
in here lista is passed to my partial view _Dodatki, and it is populated with context property Indeks and model property Dzien.
Thanks guys for your help #Wndrr , #GTown-Coder.

ASP.Net MVC Refresh Page without destroying ViewModel

I want to create a multilingual webpage. To switch between languages I've got a dropdown on my page. If the change event of the dropdown gets fired the Method called "ChangeLanguage" in my Controller is called.
public ViewModels.HomeViewModel HVM { get; private set; }
// GET: Home
public ActionResult Index()
{
this.HVM = new ViewModels.HomeViewModel();
return View(this.HVM);
}
public JsonResult ChangeLanguage(int id) {
return Json(new {Success = true});
}
Now I'd like to to change my "SelectedLanguage" Property in my ViewModel (HVM) - but the Reference is null. May anyone explain why HVM is null in my ChangeLanguage Method?
After my SelectedLanguage Property is changed I'd like to reload my whole page to display it's texts in another language
e.g.
#model ViewModels.HomeViewModel
<html>
<div class="HeaderText">
Text = #{
#Model.TextToDisplay.Where(o =>
o.Language.Equals(Model.SelectedLanguage)).First()
}
</div>
Here's what I want to do in PseudoCode:
PseudoCode:
public JsonResult ChangeLanguage(int id) {
this.HVM.SelectedLanguage =
this.HVM.AvailableLanguages.Where(o =>
o.ID.Equals(id)).First();
Page.Reload();
return Json(new {Success = true});
}
May anyone explain why HVM is null in my ChangeLanguage Method?
Adhering to stateless nature of HTTP protocol, all (unless explicitly added into request header) requests (MVC method calls) loose state data associated with it. Web server treats every request a new request and creates new instances of classes right from controller itself.
In your case since it is a new request, controller has a HVM property defined but in ChangeLanguage it is not instantiated (it gets instantiated only into Index method which is not called when you invoke ChangeLanguage) hence it is null.
After my SelectedLanguage Property is changed I'd like to reload my
whole page to display it's texts in another language.
Option 1: Refresh page
Simple option to implement. Pass the language selection to server, server will return a new view with specific data. Drawback, whole page will refresh.
Option 2: Update view selectively
If option 1 is really not acceptable, then consider this option. There are multiple ways you can achieve it. Basically it involves either (a) breaking you view into partial view and update only the portion that is affect by selection or (b) bind data element with a JS object.
(a) - Not much need to be said for this.
(b) - Data binding can easily be done if you employ a JS library like KnockoutJS.
Change your methods to these methods , This trick will work for you =>pass your model to Change language from view. Also update JsonResult to ActionResult.
public ActionResult ChangeLanguage(ViewModels.HomeViewModel model,int id)
{
this.HVM.SelectedLanguage =
this.HVM.AvailableLanguages.Where(o =>
o.ID.Equals(id)).First();
return RedirectToAction("Index",model);
}
public ActionResult Index(ViewModels.HomeViewModel model)
{
if(model == null)
{
this.HVM = new ViewModels.HomeViewModel();
}
return View(this.HVM);
}

.NET MVC 4 - Multiple "actions" on same Controller, how?

I'm pulling my hair out over this one and I'm looking for guidance before I start fudging together my own approach.
Here's what I've got:
View snippet
<td>#Html.ActionLink("More Details", "Index", new { id = product.ProductId })</td>
<td>#Html.ActionLink("Compare", "Compare", new { id = product.ProductId, compare = true })</td>
Controller snippet
public ActionResult Index(FormCollection values)
{
// Does stuff, works
}
public ActionResult Index(int productId)
{
// Does stuff, works
}
Now, here lies my problem. The Index functions are both taken now, from the POST to the form, and the "More Details" ActionLink being clicked. This works fine.
Now I want "Compare" to be functional, in which I want on the same page and will hold a list of compared products, which is fine. But how do I get that Compare functionality on the same View/Page?!
I've tried:
public ActionResult Compare(int productId)
{
}
But obviously that doesn't work as it requires a Compare.cshtml, which I don't want to happen. I want it to be modify my ViewModel and return it with newly Compared products, so I'd be able to do this from my original View:
#foreach(var products in Model.ComparedProducts)
The only way I can see me doing this is "fudging it" to have:
public ActionResult Index(int productId = 0, bool compare)
{
}
Which could become unruly with lots of functionality on the same page.
Surely there's something obvious I'm missing here?
Oh, and the AjaxLink options isn't right for me, as this is part of the site that has to work via postbacks (Progress Enhancement and all that jazz).
I think you return an ActionResult by calling return View(model), is that right? Without naming a view explicitly, the MVC resolving mechanism looks for views with the same name as the action, in your case "Compare.cshtml".
If you change your call to return View("Index", model) you will be using the Index.cshtml view regardless of the action name.
Is that what you were looking for?
An action in MVC does not require a corresponding View. It can return any view by supplying a name parameter to the View() function - see http://msdn.microsoft.com/en-us/library/system.web.mvc.controller.view%28v=vs.98%29.aspx for details.
You can in your compare function do all the logic required and redirect back to the index action or any other that display the page as needed using RedirectToAction().

Categories