I have a Partial View for showing the list of products. In this view I have ActionLinks above each column to handle sorting. There are total 4 of them and they are all identical to below one:
<div class="col-xs-2">
#Html.ActionLink(Html.DisplayNameFor(m => m.ProductName).ToHtmlString(), "Products", AppendQueryString.AppendQuery("sortOrder", ViewData["ProductNameSort"].ToString()))
</div>
In order to be able to append the current URL without losing any previously querystrings, I created a static class AppendQueryString and a method in it called AppendQuery. The class looks like below:
public static RouteValueDictionary AppendQuery(string key, string value)
{
RouteValueDictionary routeValues = new RouteValueDictionary();
var context = HttpContext.Current;
var req = context.Request.QueryString;
foreach (string qkey in req)
{
routeValues[qkey] = req[qkey];
}
if (!routeValues.ContainsKey(key))
{
routeValues.Add(key, value);
}
else
{
routeValues.Remove(key);
routeValues.Add(key, value);
}
return routeValues;
}
First I didn't realize what was the problem but something was trying to rewrite the same key value sent into this method so I had to use that ugly if-else there to add the key-value into the RouteValueDictionary properly and continue on with the project.
However, now the problem is obvious that for some reason, on page loading these four ActionLinks I have are being fired one after another so the AppendQuery method is being called 4 times on page load. The main View that renders this partial view is being called only once I am sure about it. Any of you have any idea why is this happening ? Thanks in advance.
Related
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...
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.
I have a view that loads a set of modules (we will say other partial views) when the view renders.
So think of it like this in the master view:
<div>
#Html.PartialView("~/Views/MyApp/Partials/Module1.cshtml");
</div>
<div>
#Html.PartialView("~/Views/MyApp/Partials/Module2.cshtml");
</div>
<div>
#Html.PartialView("~/Views/MyApp/Partials/Module3.cshtml");
</div>
There is a model value that is altered in the partial view Module2.cshtml. Actually it is altered in the action for the Module2 view. I am setting the model value in public ActionResult RenderChecklist(Model pageModel, IGrouping<string, ChecklistItem> list, int checklistCount):
if (itemCounter == itemCheckedCounter)
{
priorityCounter++;
pageModel.MyAppInfo.ChecklistPriorityLevel = priorityCounter;
listRestrict = "no-restrict";
overlayShow = "hidden";
}
else
{
listRestrict = "no-restrict";
overlayShow = "hidden";
}
Depending on the ChecklistPriorityLevel value is determines if an overlay is shown in Module1, Module3, etc., but since Module1 loads before Module2, the value of of ChecklistPriorityLevel in Module1 is always initiated at 0.
The code in the partial view that is called in each module is something like this:
#if (moduleRestrict && !(moduleRestrictPriorityLevel <= checklistPriority) && !Model.GetValue<bool>("moduleRestrictBypass"))
{
const string moduleLockMessage = "This section is locked.";
<div class="module overlay show">
<img src="/assets/myapp/images/lock.png" alt="Module Lock">
<p>#moduleLockMessage</p>
</div>
}
The relative code in the model is just a regular get, set at this moment:
namespace MyApp.Core.MySite.Models
{
/// <summary>
/// Model for MySite.
/// </summary>
public class MyAppInfoModel
{
... //other models
[Ignore]
public int ChecklistPriorityLevel { get; set; }
}
}
So my question is how do I get the change in the value of this model to trigger the change in other modules (partial views) that have already loaded?
DISCLAIMER: I changed some of my actual code for privacy purposes. I am just trying to give enough information to have viewers understand what I am trying to do. I am looking for the best option, whether it is asynchronous, or whatever, to properly get the value in other partial views regardless of which partials load first.
Fix for my own issue (But I would still really like to see how this could be handled)
So for my particular problem I decided that I would just force the value of the model to be set before loading any of the modules. The way my app works, Module2 could be in any spot actually, and any modules could be ahead or behind Module2. What order the modules are in is determined in the backoffice. So I just decided to create a SurfaceController and get the data (checklists) on the main view, but then I have to get the data again in "Module2", which is my ChecklistModule. I don't really like having to iterate the checklists twice, but in order to get that ChecklistPriorityLevel value I have to iterate through the checklists.
So in my main view a call the following:
MyAppChecklistController.SetChecklistPriorityLevel(Model);
Then my method is:
public static void SetChecklistPriorityLevel(MyAppRenderModel pageModel)
{
var checklist = GetCheckList(pageModel);
var priorityCounter = 1;
foreach (var list in checklist)
{
var listCount = list.Count();
var itemData = new ValueTuple<int, int, string>(1, 1, null);
itemData = list.OrderBy(x => x.IsChecked).ThenBy(x => x.SortOrder).Aggregate(itemData,
(current, item) => GetItemList(item, current.Item1, current.Item2, current.Item3, listCount));
var itemCounter = itemData.Item1;
var itemCheckedCounter = itemData.Item2;
// var itemList = itemData.Item3;
priorityCounter = GetPriorityLevel(itemCounter, itemCheckedCounter, priorityCounter);
pageModel.ChecklistPriorityLevel = priorityCounter;
}
}
Then when I render the checklist in the ChecklistModule partial view:
[ChildActionOnly]
public ActionResult RenderChecklist(IGrouping<string, ChecklistItem> list,
int checklistCount, int checklistCounter, int priorityCounter)
{
var parentFolder = list.First().UmbracoContent.Parent;
var listCount = list.Count();
var itemData = new ValueTuple<int, int, string>(1, 1, null);
var color = GetChecklistColorValue(parentFolder);
itemData = list.OrderBy(x => x.IsChecked).ThenBy(x => x.SortOrder).Aggregate(itemData,
(current, item) => GetItemList(item, current.Item1, current.Item2, current.Item3, listCount));
var itemCounter = itemData.Item1;
var itemCheckedCounter = itemData.Item2;
var itemList = itemData.Item3;
var checklistDict = GetChecklistRestrictions(parentFolder, itemCounter,
itemCheckedCounter, priorityCounter);
checklistDict.Add("color", color);
checklistDict.Add("itemList", itemList);
checklistDict.Add("checklistCounter", checklistCounter);
return GetChecklistLayout(checklistCount, checklistLayoutDict);
}
So as you can see I am pretty much running through the checklist twice in one page load. I didn't want to have to do that. If anyone has a better idea to make this more efficient, let me know.
UPDATE: The above solution (even though only part of the code is posted) fixed my issue, however I decided that I would just catch the checklist on the view and add it to the models too, then use that model (pageModel.Checklists) in the partial, so I am not actually grabbing the checklist twice with Linq. I still have to iterate through the checklists twice, but I am not grabbing the values twice. So still more of a hack, but maybe I will keep finding a way later to streamline this.
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);
}
I have been introduced to Razor as applied with MVC 3 this morning, so please forgive me if my question seems terribly uninformed!
I am working with an app whose workflow involves allowing a user to select a value (warehouse) from a drop down list, and add a record (material) from that warehouse to another record (Materials Request). Once the first material has been added to the Materials Request, I need to permanently set the value of the drop down to the warehouse that was first selected, then disable the drop down control (or set to read only, perhaps). The existing code in the razor file uses the DropDownListFor() method, including a ViewBag collection of Warehouse records. I have seen discussions which suggest abandoning the ViewBag design, but honestly I don't have the desire to rewrite major portions of the code; at least it looks like a major rewrite from the perspective of my experience level. Here's the original code:
#Html.LabelPlusFor(m => m.WarehouseId, "*:")
#Html.DropDownListFor(m => m.WarehouseId, (IEnumerable<SelectListItem>)ViewBag.WarehouseCodes, "")<br />
I believe I have been able to select a value based on a session object, though I'm still not sure how to disable the control. Here's my change:
#{
int SelectedWarehouseId = -1;
if (HttpContext.Current.Session["SelectedWarehouseId"] != null)
{
SelectedWarehouseId = Int32.Parse(HttpContext.Current.Session["SelectedWarehouseId"].ToString());
}
}
#Html.LabelPlusFor(m => m.WarehouseId, "*:")
#{
if (SelectedWarehouseId > -1)
{
#Html.DropDownListFor(m => m.WarehouseId, new SelectList((IEnumerable<SelectListItem>)ViewBag.WarehouseCodes, "WarehouseId", "WarehouseDescription", (int)SelectedWarehouseId))<br />
}
else
{
#Html.DropDownListFor(m => m.WarehouseId, (IEnumerable<SelectListItem>)ViewBag.WarehouseCodes, "")<br />
}
}
When the material is added to the Material Request, the WarehouseId is passed to the controller and I can access that value as "model.WarehouseId" in the controller class. However, I'm not sure how to get that value back to the View (apologies for the large code block here):
[HttpPost]
[TmsAuthorize]
public ActionResult Create(ItemRequestViewModel model)
{
string deleteKey = null;
//Removed code
else if (Request.Form["AddToRequest"] != null)
{
// If the user clicked the Add to Request button, we are only
// interested in validating the following fields. Therefore,
// we remove the other fields from the ModelState.
string[] keys = ModelState.Keys.ToArray();
foreach (string key in keys)
{
if (!_addToRequestFields.Contains(key))
ModelState.Remove(key);
}
// Validate the Item Number against the database - no sense
// doing this if the ModelState is already invalid.
if (ModelState.IsValid)
{
_codes.ValidateMaterial("ItemNumber", model.ItemNumber, model.WarehouseId);
Session["SelectedWarehouseId"] = model.WarehouseId;
}
if (ModelState.IsValid)
{
// Add the new Item Request to the list
model.Items.Add(new ItemViewModel() { ItemNumber = model.ItemNumber, Quantity = model.Quantity.Value, WarehouseId = model.WarehouseId });
ModelState.Clear();
model.ItemNumber = null;
model.Quantity = null;
model.WarehouseId = null;
}
}
//Removed code
return CreateInternal(model);
}
private ActionResult CreateInternal(ItemRequestViewModel model)
{
if (model != null)
{
if (!String.IsNullOrEmpty(model.SiteId))
{
ViewBag.BuildingCodes = _codes.GetBuildingCodes(model.SiteId, false);
if (!String.IsNullOrEmpty(model.BuildingId))
ViewBag.LocationCodes = _codes.GetLocationCodes(model.SiteId, model.BuildingId, false);
}
//Removed code
}
//Removed code
ViewBag.WarehouseCodes = _codes.GetWarehouseCodes(false);
return View("Create", model);
}
So my questions are, how do I disable the drop down list, and how can I pass a value for the selected WarehouseId back to the view? I've also considered adding the value to the ViewBag, but to be honest I don't know enough about the ViewBag to recognize any unintended consequences I may face by just randomly modifying it's contents.
Thanks for any help offered on this.
Without going into which approach is better...
Your dropdown should be rendered as an HTML select element, in order to disable this you'll need to add a disabled="disabled" attribute to it.
The DropDownListFor method has a htmlAttributes parameter, which you can use to achieve this:
new { disabled = "disabled" }
when your pass model to your view like
return View("Create", model);
if WareHouseID is set in model then
Html.DropDownListFor(x=>x.WareHouseID, ...)
will automatically set the selected value and u don't have to do that session processing for this. So far as disabling a field is required, stewart is right. you can disable drop down this way but then it won't be posted to the server when u submit the form. you can set it to readonly mode like
new{#readonly = "readOnly"}