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"}
Related
I'm teaching myself C# and MVC but have a background in SQL. When updating an existing master-detail set of records in a single action (let's say for instance a customer order and order details), updating the master record is no problem. Regarding the detail records, I'm seeing examples that simply delete all existing details and then add them back in rather than add, delete or update only what's changed. That seems easy and effective but involves unnecessary changes to database records and might be an issue in complex relationships.
I've tried writing code that checks the existing values against posted values to determine the right EntityState (Added, Deleted, Modified, Unchanged) for each detail. Accomplishing this using LINQ Except and Intersect works but seems to cause an unexpected performance hit.
(Instead, I could load the original values in an "oldValue" hidden field in the original GET request to compare to the POST values except that would be unreliable in a multi-user environment and seems like a bad idea.)
I'll be happy to provide code examples, but my question is more about best practices. Is there a preferred method for updating existing master-detail sets of records?
EDIT: I've added the code below in response to questions. In this example, our application allows additional attributes to be attached to a product, kept in a separate table ProductAttributes. The view allows the user to edit both the product and the attributes on the same webpage and save at the same time. The code works fine but seems slow and lags at SaveChanges.
public ActionResult Edit(Product product)
{
if (ModelState.IsValid)
{
db.Entry(product).State = EntityState.Modified;
// Establish entity states for product attributes.
List<ProductAttribute> existingAttributes = new List<ProductAttribute>();
existingAttributes = db.ProductAttributes.AsNoTracking()
.Where(x => x.Sku == product.Sku).ToList();
// Review each attribute that DID NOT previously exist.
foreach (ProductAttribute pa in product.ProductAttributes
.Except(existingAttributes, new ProductAttributeComparer()))
{
if (pa.Value is null)
{
// Value didn't exist and still doesn't.
db.Entry(pa).State = EntityState.Unchanged;
}
else
{
// New value exists that didn't before.
db.Entry(pa).State = EntityState.Added;
}
}
// Review each attribute that DID previously exist.
foreach (ProductAttribute pa in product.ProductAttributes
.Intersect(existingAttributes, new ProductAttributeComparer()))
{
if (pa.Value is null)
{
// Value existed and has been cleared.
db.Entry(pa).State = EntityState.Deleted;
}
else
{
if (pa.Value != existingAttributes
.FirstOrDefault(x => x.Attribute == pa.Attribute).Value)
{
// Value has been altered.
db.Entry(pa).State = EntityState.Modified;
}
else
{
// Value has not been altered.
db.Entry(pa).State = EntityState.Unchanged;
}
}
}
db.SaveChanges();
return RedirectToAction("Details", new { id = product.ProductId });
}
return View(product);
}
internal class ProductAttributeComparer : IEqualityComparer<ProductAttribute>
{
public bool Equals(ProductAttribute x, ProductAttribute y)
{
if (string.Equals(x.Attribute, y.Attribute,
StringComparison.OrdinalIgnoreCase))
{
return true;
}
return false;
}
public int GetHashCode(ProductAttribute obj)
{
return obj.Attribute.GetHashCode();
}
}
}
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 creating an CRUD Application in Asp.Net Core
After Add Operation I am redirecting to same view with setting model value as null to get another entry
Below is my code
public IActionResult Add(OptionMasterVM model)
{
try
{
model.QuestionList = context.QuestionMaster.Select(x => new SelectListItem { Text = x.QuestionName, Value = x.QuestionId.ToString() }).ToList();
if (HttpContext.Request.Method == "POST")
{
OptionMaster _optionmaster = new OptionMaster();
_optionmaster = model.OptionMaster;
using (var ctx = new QuestionnaireEntities(_configuration))
{
ctx.OptionMaster.Add(_optionmaster);
ctx.SaveChanges();
}
TempData["Msg"] = "Option Added Successfully , Add Another Option";
model.OptionMaster.OptionValue = string.Empty;
model.OptionMaster.OptionRating = 0;
return View(model);
}
}
catch (Exception ex)
{
logger.LogError(ex);
}
finally
{
}
return View(model);
}
Here I am setting Option Value to empty and rating to Zero to take next entry , but on view it does not show empty and zero , on view it show previously filled value.
After Setting below code these two fields should be reset but they don't
model.OptionMaster.OptionValue = string.Empty;
model.OptionMaster.OptionRating = 0;
Is there any other way to set model object as null in Asp.net Core ?
This can happen because Razor helpers use values from ModelState, rather than the model itself. Your OptionValue is probably displayed using a helper, for example:
#Html.TextBoxFor(m => m.OptionMaster.OptionValue)
When you change model values within an action, you need remove the old values from ModelState before rendering the View.
The easiest way of doing this is to call ModelState.Clear()
model.OptionMaster.OptionValue = string.Empty;
model.OptionMaster.OptionRating = 0;
ModelState.Clear(); // ensure these changes are rendered in the View
return View(model);
The values displayed for bound form fields come from ModelState, which is composed based on values from Request, ViewData/ViewBag, and finally Model. After posting, obviously, you'll have values set in Request, which will therefore be the values in ModelState. It works this way, so that when there's a validation error and the user is returned to the form to correct their mistakes, the values they posted will be there for them to edit.
Long and short, you need to follow the PRG (Post-Redirect-Get) pattern. Essentially, after posting, you only return the view on error. If the post is successful, you redirect. This not only clears ModelState, but also prevents accidental re-posts if the user attempts to refresh the page.
If you want to take the user back to the same view, simply redirect to the same action, but you need to do a redirect, not return the view.
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);
}
Hi people I am new to this website was having trouble with my controller in C# MVC3 and when I gave up on looking for answers since i spent like 2 weeks on it I decided to join here.
The problem is I want a very simple confirmation message when I create a item in my application. I tried a If statement but I can't get the context correct. Can you kind people please help me thank you. My code:
//
// POST: /News/Create
[HttpPost]
public ActionResult Create(BooksItem booksitem)
{
try
{
using (var db = new BooksForever2())
{
db.NewsItems.Add(booksitem);
db.SaveChanges();
}
return RedirectToAction("Index");
}
catch
{
return View();
}
}
The create works fine I can add books and it saves but I want when it saves a message appears so it shows the user its has been saved. I have tried: Viewbag.Message("Saved")
But this does not work. Any help will be truly appreciated
Thank You
Just add this in you controller
TempData["Message"] = "Saved";
then in your view:
#if(TempData["Message"] != null)
{
<p>#TempData["Message"].ToString()</b> #* or whatever element you need to show*#
}
at your view level you can do anything with the message (maybe flash it with jQuery):
jquery: Flash messages
UPDATE: I replaced ViewBag with TempData because I noticed you are doing a redirect, in which case the ViewBag won't persist but TemData would
Where do you want that confirmation message displayed? On the same edit form you are already on, or back on the index/list page?
Right now at the end of your method, you are redirecting to the Index action/page:
return RedirectToAction("Index");
The result of that is that the Index page will be loaded, and it will be completely unaware of where it came from other that something was saved.
Your two options, as I see it, are:
1) Stay on the current page, and display the message. You can add that message to the ViewBag like as has already been mentioned:
ViewBag.Message = "Saved"`
And then display it like this:
#if(ViewBag.Message != null)
{
<p>#ViewBag.Message</p>
}
and then make sure you remove the RedirectToAction and just return the default View, otherwise will still bounce you to the Index page.
2) Or, you can redirect the user back to the Index page, passing the message to be displayed, and then have the Index page look for that message. So when you call RedirectToAction, include a query string parameter:
ViewBag.Message
return RedirectToAction("Index", new { Message="Saved" });
Which will redirect you to ".../yourControllerName/Index?Message=Saved". Then you can add this to your Index action method:
if(!string.IsNullOrEmpty(QueryString["Message"]))
{
ViewBag.Message = QueryString["Message"];
}
And include that same view code in your index view:
#if(ViewBag.Message != null)
{
<p>#ViewBag.Message</p>
}