Imagine a view bag called
ViewBag.Modes
this contains the following:
Simple
Advanced
Manual
Complete
How can I access the viewbag by index like you would in an array?
e.g Simple is at index 0 then it would look like this
ViewBag.Modes[0]
I tried the above but it doesn't work so...How can I replicate this with viewbag or is there a workaround I can use?
This does the trick for me:
Controller:
public ActionResult Index()
{
var stringArray = new string[3] { "Manual", "Semi", "Auto"};
ViewBag.Collection = stringArray;
return View();
}
View:
#foreach(var str in ViewBag.Collection)
{
#Html.Raw(str); <br/>
}
#for (int i = 0; i <= 2; i++ )
{
#Html.Raw(ViewBag.Collection[i]) <br/>
}
Output:
Sorry for not using your terms. I was scrachting this together from the top of my head.
ViewBag is a dynamic property, which complicates things a bit.
For the behavior you're looking for, you might want to use ViewData instead. ViewData is a dictionary, which means you can access the values of each index using Linq:
this.ViewData.Keys.ElementAt(0);
In your controller:
string[] Modes = {"Simple", "Advanced", "Manual", "Complete" };
ViewData["Modes"] = Modes;
In your View:
<div>
#(((string[])ViewData["Modes"])[0])
</div>
<div>
#(((string[])ViewData["Modes"])[1])
</div>
<div>
#(((string[])ViewData["Modes"])[2])
</div>
Using the ViewBag:
Controller
public ActionResult Index()
{
List<string> modes = new List<string>();
modes.Add("Simple");
modes.Add("Advanced");
modes.Add("Manual");
modes.Add("Complete");
ViewBag["Modes"] = modes;
return View();
}
View
<h1>List of Modes</h1>
#{foreach (var mode in ViewBag.Modes) {
<li>
#hobby
</li>
} }
----------------------------------------------------------------
Using the ViewData:
Controller
public ActionResult Index()
{
string[] Modes = {"Simple", "Advanced", "Manual", "Complete" };
ViewData["Modes"] = Modes;
return View();
}
**View
<h1>List of Modes</h1>
#foreach (string mode in ViewData["Modes"] as string[]) {
<li>
#mode
</li>
}
Thanks for the posts but shortly after writing this I came up with this solution using a model & list as below this could also be done using viewbag instead of model.
List<string> list = new List<string>();
foreach (Models.AppModeInfo blah in Model.theAppModes)
{
list.Add(blah.Name);
}
var AppModeText = "";
switch (item.AppModeId)
{
case 1:
AppModeText = list[0];
break;
case 2:
AppModeText = list[1];
break;
case 3:
AppModeText = list[2];
break;
case 4:
AppModeText = list[3];
break;
case 5:
AppModeText = list[4];
break;
}
Related
I am a newbie to spring boot and thymeleaf,
I have a list of books ina table with checkboxes, I am not sure how to pass selected booksId s from the view to the controller and use them by the borrow or Return bttons? could you please help?
Here is my Html file https://wtools.io/paste-code/b5g4
and this is the relevant part from my bookService implementation :
public void borrowBook(String userEmail, String bookIds, Model model) {
if (!CollectionUtils.isEmpty(books)) {
User user = userRepository.findByEmail(userEmail);
List<String> requestedBooks = getRequestedBookIds(bookIds);
List<Book> borrowedBooks = new ArrayList<>();
List<Book> invalidBooks = new ArrayList<>();
for (Book book : books) {
if (requestedBooks.contains(book.getId()) && !book.isBorrowed() && user != null) {
book.setBorrowed(true);
book.setBorrowedBy(user.getFirstName());
borrowedBooks.add(book);
model.addAttribute("bookStatus", "Book BOrrowed By " + user.getFirstName());
} else {
invalidBooks.add(book);
model.addAttribute("bookStatus", "No Books are available");
}
}
model.addAttribute("inValidBooks", invalidBooks);
model.addAttribute("bookList", borrowedBooks);
}
}
#SuppressWarnings("unchecked")
private List<String> getRequestedBookIds(String bookIds) {
List<String> requestedBookIds = null;
try {
requestedBookIds = new ObjectMapper().readValue(bookIds, ArrayList.class);
} catch (Exception e) {
e.printStackTrace();
}
return !CollectionUtils.isEmpty(requestedBookIds) ? requestedBookIds : new ArrayList<>();
}
and this is from the controller:
#GetMapping(value = "/available", produces = MediaType.APPLICATION_JSON_VALUE)
public String getAvailableFreeBooks(Model model) {
List<Book> availableBooks= bookService.getAllAvailaBooks();
model.addAttribute("listBooks", availableBooks);
return "available_books";
}
In your html you would probably:
<input type="checkbox" th:field="*{requestedBooks}" value="${book.getId}">
omit the id (if you don't need it).
use th:field (instead of name).
set value to the id of the current book.
In your controller: requestedBooks (#ModelAttribute("requestedBooks") List<String> requestedBooks) will (should) contain all checked book ids.
Ref: https://www.thymeleaf.org/doc/tutorials/2.1/thymeleafspring.html#checkbox-fields
A sample repository:
https://github.com/xerx593/soq67602860
Uppdate:
To process the checkboxes client-sided (jquery),
you can obtain an array of ids like:
<script language="javascript" type="text/javascript">
$(document).ready(function () {
$("#btnBorrow").click(function() {
var reqBookIds = new Array();
$('input[name="requestedBooks"]:checked').each(function() {
reqBookIds .push(this.value);
});
alert("Number of selected Books: "+reqBookIds .length+"\n"+"And, they are: "+reqBookIds);
// do stuff with reqBookIds ...
)};
});
</script>
With the mentioned <input type="checkbox" .../> (consider that <input/> should be inside a <form/>!!) and a button like:
<button id="btnBorrow">Borrow</button>
..the userEmail must come from client side???
This weekend a lot of struggle with a View Component.
I try to add a dropdownlist that does an auto postback onchange. This dropdownlist is on a view component.
I have 2 problems:
I don't get the asp-page-handler after the post, does it work like I implemented it on the form-tag?
Post calls method public void OnPost on razor page containing view
component. I would think it would be better to have a method on the
View Component like OnChangeProject?
The code of my View (View Component):
<form asp-page-handler="ChangeProject" method="post">
#Html.AntiForgeryToken()
#Html.DropDownList("id", new SelectList(Model, "Id", "Id"), new { onchange = "this.form.submit()" })
</form>
Thanks in advance!!
I exprienced the same problem and the way i fixed it is already answered in your question.
The form call is made at the page where you got your View Component embedded. I don't think it would be even possible to call a handler in your View Component with asp-page-handler as this is Razor Pages tag helper.
The way i got it work is simply putting the page-handler method on the PageModel that is embedding the View Component. In your case you can simply implement this handler on your Razor Page:
public IActionResult OnPostChangeProject()
{
// ... do Something
}
I don't know though how it would work to trigger a controller method in your View Component. Possibly create a new Controller class and route to it with asp-controller and asp-action in your form tag.
You should remember that the Page handlers could be viewed as convenience methods.
All the ASP.Net Core framework does is looks at the Query string parameters and Form data and translates it into Page handler calls.
And even though the Handlers are not available in View Components or Partial Views you still can get access to all the required ingredients by injecting IHttpContextAccessor into the View.
It will provide you with HttpContext.Request which contains both the Query and the Form properties.
You can then create your own Handler mapper. Here is one, for example:
public class HandlerMapping
{
public string Name { get; set; }
public System.Delegate RunDelegate { get; set; }
public HandlerMapping(string name, Delegate runDelegate)
{
RunDelegate = runDelegate;
Name = name;
}
}
public class PartialHandlerMapper
{
IHttpContextAccessor _contextAccessor;
public PartialHandlerMapper(IHttpContextAccessor contextAccessor)
{
_contextAccessor = contextAccessor;
}
public void RouteHandler(List<HandlerMapping> handlerMappings, string PartialDescriminatorString = null)
{
var handlerName = _contextAccessor.HttpContext.Request.Query["handler"];
var handlerMapping = handlerMappings.FirstOrDefault(x => x.Name == handlerName);
if (handlerMapping != null)
{
IFormCollection form;
try
{
form = _contextAccessor.HttpContext.Request.Form;
}
catch
{
return;
}
if (!string.IsNullOrWhiteSpace(PartialDescriminatorString) && form[nameof(PartialDescriminatorString)] != PartialDescriminatorString)
return;
List<Object> handlerArgs = new List<object>();
var prmtrs = handlerMapping.RunDelegate.Method.GetParameters();
foreach (var p in prmtrs)
{
object nv = null;
var formValue = form[p.Name];
if (!StringValues.IsNullOrEmpty(formValue))
{
try
{
nv = TypeDescriptor.GetConverter(p.ParameterType).ConvertFromString(formValue);
}
catch (FormatException)
{
//throw new FormatException($"Could not cast form value '{formValue}' to parameter {p.Name} (type {p.ParameterType}) of handler {handlerName}. Make sure you use correct type parameter. ");
nv = Activator.CreateInstance(p.ParameterType);
}
catch (ArgumentException)
{
nv = Activator.CreateInstance(p.ParameterType);
}
}
else
nv = Activator.CreateInstance(p.ParameterType);
handlerArgs.Add(nv);
}
handlerMapping.RunDelegate.DynamicInvoke(handlerArgs.ToArray());
}
}
}
And inject it into the service container:
services.AddScoped<PartialHandlerMapper>();
And here is a shopping cart partial view code section example:
#inject ShoppingManager shoppingManager
#inject PartialHandlerMapper partialHandlerMappping
#{
string ToggleCartItemTrialUseHandler = nameof(ToggleCartItemTrialUseHandler);
string DeleteCartItemHandler = nameof(DeleteCartItemHandler);
List<HandlerMapping> handlerMappings = new List<HandlerMapping> {
new HandlerMapping (ToggleCartItemTrialUseHandler, (Guid? PicID, bool? CurrentValue) => {
if (PicID == null || CurrentValue == null)
return;
shoppingManager.UpdateTrial((Guid)PicID, !(bool)CurrentValue);
}),
new HandlerMapping (DeleteCartItemHandler, (Guid? PicID) => {
if (PicID == null)
return;
shoppingManager.RemoveProductFromCart((Guid)PicID);
})
};
partialHandlerMappping.RouteHandler(handlerMappings);
var cart = shoppingManager.GetSessionCart();
}
Form element example from the same view:
<td align="center" valign="middle">
<form asp-page-handler="#DeleteCartItemHandler">
<input name=PicID type="hidden" value="#i.PicID" />
<button>
Delete
</button>
</form>
</td>
Where #i is an Item in the shopping cart
It's possible to create a combo (Controller/ViewComponent) by decorating the controller with a ViewComponent(Name="myviewcomponent").
Then create the invokeasync as usual, but because the controller doesn't inherit from a ViewComponent, the return result would be one of the ViewComponent result (ViewViewComponentResult, et).
The form in the viewcomponent can then have a button with asp-controller/action tag helpers targetting the controller/action.
my question is: "How can I show a alert if the modelstate is invalid?"
I have a HomeController where I check if the ModelState.IsValid. I'm using a modal to create new items.
// GET: Project/
public ActionResult Index()
{
using (var service = new Service1Client())
{
ProjectDto newProject = new ProjectDto();
newProject.StartTime = DateTime.Now;
var details = service.GetAllRequirementDetails();
var list = new SelectList(details, "Id", "Title");
var projects = service.GetAllProjects().ToList();
var vm = new ProjectIndexViewModel(list, newProject);
vm.Projects = projects;
return View(vm);
}
}
// POST: Project/Create/
[HttpPost]
public ActionResult Index(CreateProjectViewModel vm)
{
if (ModelState.IsValid)
{
using (var service = new Service1Client())
{
service.CreateProject(vm.NewProject);
return RedirectToAction(RedirectString);
}
}
return RedirectToAction(RedirectString);
}
I want to add
<div class="alert alert-danger" role="alert">
Something went wrong!
</div>
Use ViewBag if returned from same method or if model state returned redirect to next method then use TempData.
Here is example code.
// POST: Project/Create/
[HttpPost]
public ActionResult Index(CreateProjectViewModel vm)
{
if (ModelState.IsValid)
{
using (var service = new Service1Client())
{
service.CreateProject(vm.NewProject);
return RedirectToAction(RedirectString);
}
}
ViewBag.Message = "Something went wrong";//if it is redirecting to some other action then use TempData
return View(vm);
}
In view part
#if(!string.isNotNullorEmpty(ViewBag.Message){
<div class="alert alert-danger" role="alert">
<a href="#" class="alert-link">
#ViewBag.Message</a>
</div>
}
If you are not posting your form via ajax, what you can do is to set a flag to indicate that the validation failed and in your page, check the value of that and execute your js code to show the bootstrap alert.
If ModelState.IsValid is false, you should not be redirecting, you should be returning the posted model to the same view.
[HttpPost]
public ActionResult Index(CreateProjectViewModel vm)
{
if (ModelState.IsValid)
{
using (var service = new Service1Client())
{
service.CreateProject(vm.NewProject);
return RedirectToAction(RedirectString);
}
}
ViewBag.DidValidationFail="Yes";
return View(vm);
}
And in your Index view, render the div based on the value of this flag
#if (ViewBag.DidValidationFail == "Yes")
{
<div class="alert alert-danger" id="errorMsg" role="alert">
Something went wrong!
</div>
}
If you do not prefer to use the dynamic ViewBag, you may add a property to your viewmodel and set that property value and use that in your razor (Same as we did with ViewBag item)
You can use put your message in the else part, and go back to your view (with the posted model) instead of redirecting. I've included a line that is useful to see what went wrong while debugging. That way you can customize your message according to the errors.
[HttpPost]
public ActionResult Index(CreateProjectViewModel vm)
{
if (ModelState.IsValid)
{
using (var service = new Service1Client())
{
service.CreateProject(vm.NewProject);
return RedirectToAction(RedirectString);
}
}
else
{
ViewBag.error = "Something went wrong";
var errors = ModelState.Values.SelectMany(x => x.Errors);
return View(vm);
}
}
And in the view :
<div class="alert alert-danger" role="alert">
#ViewBag.error
</div>
This question already has answers here:
How do you create a dropdownlist from an enum in ASP.NET MVC?
(36 answers)
Closed 8 years ago.
I've been finding all over the place that the common way to bind Enums to DropDowns is through helper methods, which seems a bit overbearing for such a seemingly simple task.
What is the best way to bind Enums to DropDownLists in ASP.Net MVC 4?
You can to this:
#Html.DropDownListFor(model => model.Type, Enum.GetNames(typeof(Rewards.Models.PropertyType)).Select(e => new SelectListItem { Text = e }))
I think it is about the only (clean) way, which is a pity, but at least there are a few options out there. I'd recommend having a look at this blog: http://paulthecyclist.com/2013/05/24/enum-dropdown/
Sorry, it's too long to copy here, but the gist is that he created a new HTML helper method for this.
All the source code is available on GitHub.
Enums are supported by the framework since MVC 5.1:
#Html.EnumDropDownListFor(m => m.Palette)
Displayed text can be customized:
public enum Palette
{
[Display(Name = "Black & White")]
BlackAndWhite,
Colour
}
MSDN link: http://www.asp.net/mvc/overview/releases/mvc51-release-notes#Enum
In my Controller:
var feedTypeList = new Dictionary<short, string>();
foreach (var item in Enum.GetValues(typeof(FeedType)))
{
feedTypeList.Add((short)item, Enum.GetName(typeof(FeedType), item));
}
ViewBag.FeedTypeList = new SelectList(feedTypeList, "Key", "Value", feed.FeedType);
In my View:
#Html.DropDownList("FeedType", (SelectList)ViewBag.FeedTypeList)
The solution from PaulTheCyclist is spot on. But I wouldn't use RESX (I'd have to add a new .resx file for each new enum??)
Here is my HtmlHelper Expression:
public static MvcHtmlString EnumDropDownListFor<TModel, TEnum>(this HtmlHelper<TModel> htmlHelper,
Expression<Func<TModel, TEnum>> expression, object attributes = null)
{
//Get metadata from enum
var metadata = ModelMetadata.FromLambdaExpression(expression, htmlHelper.ViewData);
var enumType = GetNonNullableModelType(metadata);
var values = Enum.GetValues(enumType).Cast<TEnum>();
//Convert enumeration items into SelectListItems
var items =
from value in values
select new SelectListItem
{
Text = value.ToDescription(),
Value = value.ToString(),
Selected = value.Equals(metadata.Model)
};
//Check for nullable value types
if (metadata.IsNullableValueType)
{
var emptyItem = new List<SelectListItem>
{
new SelectListItem {Text = string.Empty, Value = string.Empty}
};
items = emptyItem.Concat(items);
}
//Return the regular DropDownlist helper
return htmlHelper.DropDownListFor(expression, items, attributes);
}
Here is how I declare my enums:
[Flags]
public enum LoanApplicationType
{
[Description("Undefined")]
Undefined = 0,
[Description("Personal Loan")]
PersonalLoan = 1,
[Description("Mortgage Loan")]
MortgageLoan = 2,
[Description("Vehicle Loan")]
VehicleLoan = 4,
[Description("Small Business")]
SmallBusiness = 8,
}
And here is the call from a Razor View:
<div class="control-group span2">
<div class="controls">
#Html.EnumDropDownListFor(m => m.LoanType, new { #class = "span2" })
</div>
</div>
Where #Model.LoanType is an model property of the LoanApplicationType type
UPDATE: Sorry, forgot to include code for the helper function ToDescription()
/// <summary>
/// Returns Description Attribute information for an Enum value
/// </summary>
/// <param name="value"></param>
/// <returns></returns>
public static string ToDescription(this Enum value)
{
if (value == null)
{
return string.Empty;
}
var attributes = (DescriptionAttribute[]) value.GetType().GetField(
Convert.ToString(value)).GetCustomAttributes(typeof (DescriptionAttribute), false);
return attributes.Length > 0 ? attributes[0].Description : Convert.ToString(value);
}
Technically, you don't need a helper method, since Html.DropdownListFor only requires a SelectList or Ienumerable<SelectListItem>. You can just turn your enums into such an output and feed it in that way.
I use a static library method to convert enums into List<SelectListItem> with a few params/options:
public static List<SelectListItem> GetEnumsByType<T>(bool useFriendlyName = false, List<T> exclude = null,
List<T> eachSelected = null, bool useIntValue = true) where T : struct, IConvertible
{
var enumList = from enumItem in EnumUtil.GetEnumValuesFor<T>()
where (exclude == null || !exclude.Contains(enumItem))
select enumItem;
var list = new List<SelectListItem>();
foreach (var item in enumList)
{
var selItem = new SelectListItem();
selItem.Text = (useFriendlyName) ? item.ToFriendlyString() : item.ToString();
selItem.Value = (useIntValue) ? item.To<int>().ToString() : item.ToString();
if (eachSelected != null && eachSelected.Contains(item))
selItem.Selected = true;
list.Add(selItem);
}
return list;
}
public static class EnumUtil
{
public static IEnumerable<T> GetEnumValuesFor<T>()
{
return Enum.GetValues(typeof(T)).Cast<T>();
}
// other stuff in here too...
}
/// <summary>
/// Turns Camelcase or underscore separated phrases into properly spaces phrases
/// "DogWithMustard".ToFriendlyString() == "Dog With Mustard"
/// </summary>
public static string ToFriendlyString(this object o)
{
var s = o.ToString();
s = s.Replace("__", " / ").Replace("_", " ");
char[] origArray = s.ToCharArray();
List<char> newCharList = new List<char>();
for (int i = 0; i < origArray.Count(); i++)
{
if (origArray[i].ToString() == origArray[i].ToString().ToUpper())
{
newCharList.Add(' ');
}
newCharList.Add(origArray[i]);
}
s = new string(newCharList.ToArray()).TrimStart();
return s;
}
Your ViewModel can pass in the options you want. Here's a fairly complex one:
public IEnumerable<SelectListItem> PaymentMethodChoices
{
get
{
var exclusions = new List<Membership.Payment.PaymentMethod> { Membership.Payment.PaymentMethod.Unknown, Membership.Payment.PaymentMethod.Reversal };
var selected = new List<Membership.Payment.PaymentMethod> { this.SelectedPaymentMethod };
return GetEnumsByType<Membership.Payment.PaymentMethod>(useFriendlyName: true, exclude: exclusions, eachSelected: selected);
}
}
So you wire your View's DropDownList against that IEnumerable<SelectListItem> property.
Extending the html helper to do it works well, but if you'd like to be able to change the text of the drop down values based on DisplayAttribute mappings, then you would need to modify it similar to this,
(Do this pre MVC 5.1, it's included in 5.1+)
public static IHtmlString EnumDropDownListFor<TModel, TEnum>(this HtmlHelper<TModel> html, Expression<Func<TModel, TEnum>> expression)
{
var metadata = ModelMetadata.FromLambdaExpression(expression, html.ViewData);
var enumType = Nullable.GetUnderlyingType(metadata.ModelType) ?? metadata.ModelType;
var enumValues = Enum.GetValues(enumType).Cast<object>();
var items = enumValues.Select(item =>
{
var type = item.GetType();
var member = type.GetMember(item.ToString());
var attribute = member[0].GetCustomAttribute<DisplayAttribute>();
string text = attribute != null ? ((DisplayAttribute)attribute).Name : item.ToString();
string value = ((int)item).ToString();
bool selected = item.Equals(metadata.Model);
return new SelectListItem
{
Text = text,
Value = value,
Selected = selected
};
});
return html.DropDownListFor(expression, items, string.Empty, null);
}
I have an ASP.NET MVC 3 Controller Action that accepts an object as follows:
//// model.ReferenceNumber equals "-1"
public ActionResult EditApplication(EditApplicationViewModel model)
{
if (!ModelState.IsValid)
{
...
}
// Existing Applicant
model.ReferenceNumber = "";
var msg = this.RenderPartialViewToString("_ExistingApplication", model);
}
When the request is received, the "model.ReferenceNumber" equals "-1". Even after setting the property on the model to empty string "", still the results of "RenderPartialViewToString" shows the value of "-1" in the "Value" property of the "input" HTML field.
Any idea why this is happening?
This is the code used to render the partial view:
http://craftycodeblog.com/2010/05/15/asp-net-mvc-render-partial-view-to-string/
public static string RenderPartialViewToString(this Controller controller, string viewName, object model)
{
if (string.IsNullOrEmpty(viewName))
viewName = controller.ControllerContext.RouteData.GetRequiredString("action");
controller.ViewData.Model = model;
using (var sw = new StringWriter())
{
var viewResult = ViewEngines.Engines.FindPartialView(controller.ControllerContext, viewName);
var viewContext = new ViewContext(controller.ControllerContext, viewResult.View, controller.ViewData, controller.TempData, sw);
viewResult.View.Render(viewContext, sw);
return sw.GetStringBuilder().ToString();
}
}
Thanks
When you modify the value of some model property in your controller qction make sure you remove it from the modelstate as well:
ModelState.Remove("ReferenceNumber");
model.ReferenceNumber = "new value";
var msg = this.RenderPartialViewToString("_ExistingApplication", model);