How to display different values without refreshing the page MVC C# - c#

I have a method that is looping through a list of values, what I would like to do is when I open the page to be able to see the values changing without refreshing the current view. I've tried something like the code bellow.
public static int myValueReader { get; set; }
public static void ValueGenerator()
{
foreach (var item in myList)
{
myValue = item;
Thread.Sleep(1000);
}
}
The actual thing is I want it to read this values even if I close the form. I presume I would need to assign a Task in order to do this, but I was wandering if there is any better way of doing it since it's a MVC application?

Here's another way to do it:
use AJAX and setTimeout
declare one action in your controller (this one will return your different values)
an integer in your ViewBag, some like: ViewBag.totalItems
Declare an action in your controller: This is important, because this will be your connection with your database, or data. This action will receive the itemIndex and will return that item. Something like this:
[HttpPost]
public JsonResult GetItem(int index) {
return Json(myList.ElementAt(index));
}
ViewBag.TotalItems: Your view has to know how many Items you have in your list. I recommend you to pass that value as an integer via ViewBag:
public ActionResult Index() {
ViewBag.TotalItems = myList.Count();
return View();
}
AJAX and setTimeout: Once that you have all of this, you're ready to update your view without refreshing:
<script>
$(function() {
var totalItems = #Html.Raw(Json.Encode(ViewBag.TotalItems));
var currentItemIndex = 0;
var getData = function() {
$.post("#Url.Action("GetItem")", {index:currentItemIndex}, function(data) {
// data is myList.ElementAt(index)
// do something with it
}).always(function() {
currentItemIndex++;
if(currentItemIndex < totalItems) {
setTimeout(getData, 1000); // get the next item every 1 sec
}
})
}
getData(); // start updating
})
</script>

Your best bet as #DavidTansey mentioned is to use SignlarR. It wraps web sockets and falls back to long polling/etc if the users' browser doesn't support it. Your users will subscribe to specific channels and then you can raise events in those channels.
With regard to your business logic, you'll need to look into async programming techniques. Once you start on this, you'll probably have more specific questions.

Related

How to hide parameters from querystring (url) ASP.NET

I'm trying to hide the parameters from the querystrings in my web application.
I have been able to do that by using the session to store temporary variables. So it would work like this:
1. Click the view profile button:
href="#Url.Action("RedirectWithId", "Redirect", new { act = "ProfileView", ctrl = "User", id = member.Id})"
2. Calls the redirection method and stores the temp data:
public class RedirectController : Controller
{
public ActionResult RedirectWithId(string act, string ctrl, int id)
{
Session["temp_data"] = id;
return RedirectToAction(act, ctrl);
}
}
3. Use it in the action method without the parameter:
public ActionResult ProfileView()
{
if (Session["temp_data"] == null)
{
return Redirect(Request.UrlReferrer.ToString());
}
int id = (int)Session["temp_data"];
var model = GetUserById(id);
return View(model);
}
So it works just fine, however, this way to hide parameters doesn't handle the case where let's say I go to a first profile(id 4), and then go to a second one(id 8). If from the second profile I press the back button on the navigator trying to go back to the first profile(id 4), I'm going to be redirected to the current profile(id 8), since 8 is the current value of the Session["temp_data"].
Is there a way to handle this perticular case? Or is the another totally different and better way to hide parameters in the URL?
Thank you!
You can try this instead of Session
TempData["temp_data"]
I came to the conclusion that since I am already using authorizations and roles within my application, I don't need to always hide the parameters. I can simply hide whenever I am passing a complex object as a parameter.

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...

ASP.NET MVC 5 Keeping old input between requests

I need feature that is something similar to Laravel's old input helper but in MVC 5.
https://laravel.com/docs/5.6/requests#old-input
If validation fails, I need to reload all my model data as it was in the previous request except those inputs where user entered something wrong.
The problem is that my form has many disabled inputs and fields that program is fetching within [HttpGet] method, and they're getting lost during submission. So I need to store them in session.
The code below seems to work but is there any more efficient and beautiful way to do so with a less amount of code within each controller?
[HttpGet]
[Route(#"TaskManagement/Edit/{guid}")]
public async Task<ActionResult> Edit(Guid guid)
{
var model = new EditTaskViewModel();
model.Guid = guid;
await model.GetTaskFromRemoteService(new UserInfo(User));
ControllerHelpers.DisplayAlerts(model, this);
TempData["OldModel"] = model;
return View(model);
}
[HttpPost]
[ValidateAntiForgeryToken]
[Route(#"TaskManagement/Edit/{guid}")]
public async Task<ActionResult> Edit(EditTaskViewModel model, Guid guid, string submit)
{
model.Guid = guid;
if (ModelState.IsValid) {
await model.UpdateTaskInRemoteService(new UserInfo(User), submit);
ControllerHelpers.DisplayAlerts(model, this, "Task successfully updated");
if (model.ErrorCode == null)
return RedirectToAction("Edit", new { guid = model.Guid });
return RedirectToAction("Index");
}
if (TempData["OldModel"] != null) {
model = (EditTaskViewModel)TempData["OldModel"];
}
return View(model);
}
Using session state (including TempData) like this may break when you have multiple copies of the page open. You can work around this by generating a unique ID for the session key and storing it in a hidden field.
However, I would try to avoid using session altogether.
A simple approach is to use hidden fields to store the values that aren't sent to the server because they are in disabled fields.
A more robust approach is a separate class (or at least a private method) that knows how to setup your model for the first time and in transition (e.g. failed server validation). I call these classes "composers" and I describe the approach here.
Pseudocode for how an action method with a composer might look:
if( ModelState.IsValid ){
return Redirect();
}
var rebuiltModel = _composer.ComposeEdit( incomingModel );
return View( rebuiltModel );
I think the answer was quite simple. The shortest and easiest way is to populate the object from the database\remote service once more.
The fields that user entered whether they're valid or not will stay as they were before. The rest of them will load once again.

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

DropDownListFor SelectedValue and Disable using Session State

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"}

Categories