How can I override the name attribute of a RadioButtonFor? - html

I'm trying to group together radiobuttons that are creating using a for loop and Razor syntax. Here is the code:
#for (var i = 0; i < Model.Sessions.Count(); i++)
{
#Html.HiddenFor(it => it.Sessions[i].Id)
#Html.RadioButtonFor(it => it.Sessions[i].Checkbox, "0", new {#class = "Sessions", #id = id, #name="Sessions"})
#Html.LabelFor(it => it.Sessions[i].Name, Model.Sessions[i].Name)
<span class="time-span"><em>#Model.Sessions[i].StartTime</em><em>#Model.Sessions[i].EndTime</em></span>
<br />
}
The third line inside the for loop is where the problem is. Basically the name doesn't change and it's always "Sessions[x].Checkbox". The checkbox is a property (bool) of a custom class. I can't seem to get the hang of debugging Razor stuff, so any help would be greatly appreciated, I'm guessing this will be extremely obvious to someone here.
EDIT
Dimitrov's post helped a lot. Below is the final code I used. I use the #class and #id attributes to be able to use Javascript to select the session originally picked (since this is an edit, not create form).
#for (var i = 0; i < Model.Sessions.Count(); i++)
{
#Html.HiddenFor(it => it.Sessions[i].Id)
var SId = #Model.Sessions[i].Id;
#Html.RadioButtonFor(it => it.selectedSession, Model.Sessions[i].Id, new { id = SId, #class = "Sessions" })
#Html.LabelFor(it => it.Sessions[i].Name, Model.Sessions[i].Name)
<span class="time-span"><em>#Model.Sessions[i].StartTime</em><em>#Model.Sessions[i].EndTime</em></span>
<br />
}

If you want to be able to select only a single radio button you need to have a single property on your view model to hold the selected session id, like this:
public class SessionViewModel
{
public int SelectedSessionId { get; set; }
public IList<Session> Sessions { get; set; }
}
public class Session
{
public int Id { get; set; }
public string Name { get; set; }
}
and then have a controller:
public class HomeController : Controller
{
public ActionResult Index()
{
var model = new SessionViewModel
{
SelectedSessionId = 2,
Sessions = Enumerable.Range(1, 5).Select(x => new Session
{
Id = x,
Name = "session" + x,
}).ToList()
};
return View(model);
}
[HttpPost]
public ActionResult Index(SessionViewModel model)
{
return Content("Thank you for selecting session id: " + model.SelectedSessionId);
}
}
and finally a view:
#model SessionViewModel
#using (Html.BeginForm())
{
for (var i = 0; i < Model.Sessions.Count(); i++)
{
#Html.HiddenFor(x => x.Sessions[i].Id)
#Html.RadioButtonFor(x => x.SelectedSessionId, Model.Sessions[i].Id, new { id = "session_" + i })
#Html.Label("session_" + i, Model.Sessions[i].Name)
<br/>
}
<button type="submit">OK</button>
}

Related

How to pass value selected from DropDownList to Controller - MVC

I am having trouble getting the value selected in my dropdownlist in my Homecontroller. I have the DropDownList in a form but I think my format may be wrong. I'm new to MVC and new to HTML so I'm struggling pretty hard. Would appreciate some help.
Here is my controller (I put this in my homecontroller, is that a bad idea?):
public IActionResult Index()
{
_ = new List<MyjsonSettings>();
var obj = new StatusPortController(configuration);
List<MyjsonSettings> PortList = obj.GetPortNum();
List<SelectListItem> AppNameList = PopulateDropDown(PortList);
ViewData["Applications"] = AppNameList;
return View("~/Views/Home/dataview.cshtml");
}
public List<SelectListItem> PopulateDropDown(List<MyjsonSettings> PortList)
{
List<SelectListItem> AppNameList = new List<SelectListItem>();
for (int i = 0; i < PortList.Count(); i++)
{
AppNameList.Add(new SelectListItem {
Text = PortList[i].NAME, Value = (i+1).ToString()
});
}
return AppNameList;
}
Here is the view (dataview.cshtml):
#{
ViewData["Title"] = "Home Page";
}
#using (Html.BeginForm("Index", "Home", FormMethod.Post))
{
#Html.DropDownList("Applications", ViewData["AppNameList"] as List<SelectListItem>)
<input type="submit" value="submit" />
}
Any ideas? No errors when I run, I just don't know how to get the response back.
you can rebuild the structure to a more usable way, and in order to submit form with drop down list or any type of fields you need to first return a view with Model and then submit the form to an action that receive same Model type as parameter
example:
model:
public class ApplicationsAddModel {
public ApplicationsAddModel (){
//constructer to initialize the list
ApplicationsList = new List<SelectListItem>();
}
public string test{ get; set; }
public int selectedApplicationId { get; set; }
public List<SelectListItem> ApplicationsList { get; set; }
}
controller
//this is the first action that return the model
[HttpGet]
public IActionResult Index()
{
ApplicationsAddModel model = new ApplicationsAddModel ();
//fill your drop down list
List<SelectListItem> AppNameList = PopulateDropDown(PortList);
model.ApplicationsList = AppNameList;
return View(model);
}
[HttpPost] //recive the form
public IActionResult Index(ApplicationsAddModel SubmittedModel)
{
var selectedApplication = SubmittedModel.selectedApplicationId; //get the selected value from ddl
//fill your drop down list
List<SelectListItem> AppNameList = PopulateDropDown(PortList);
model.ApplicationsList = AppNameList;
return View(SubmittedModel);
}
view (index.cshtml):
#model projectName.ApplicationsAddModel
#{ ViewData["Title"] = "Home Page"; }
#using (Html.BeginForm("Index", "Home", FormMethod.Post))
{
#Html.LabelFor(m => m.selectedApplicationId)
#Html.DropDownListFor(m => m.selectedApplicationId, Model.ApplicationsList, "---", new { #class = "custom-select form-control" })
<input type="submit" value="submit" />
}
summary:
in MVC when you have to submit data to controller, create your model, go to controller and create your first action (GET) which fill the form with initial data and fill your drop down lists if exist, then create the (POST) action which receive the model of same type of the view and MVC will bind it automatically for you
best regards

Set MVC SelectList text to two fileds [duplicate]

How would I generate a select list, where the text field, is made up of two or more text columns, eg: Where I have a Description and Rate field in my database, I want to combine these to show:
Large--£200
Medium--£150
Small--£100
Controller code is:
var stands = db.Stands.Where(s => s.ExhibitorID == null).ToList();
ViewBag.StandID = new SelectList(stands,"StandID", "Description" + "-- £" + "Rate");
...and my view is (currently):
<div class="editor-field">
#Html.DropDownList("StandID", "--Select--")
</div>
...but the "Description" + "-- £" + "Rate"); won't run:
DataBinding:
'System.Data.Entity.DynamicProxies.Stand_63F8C9F623B3C0E57D3008A57081AFCD9C39E1A6B79B0380B60840F1EFAE9DB4'
does not contain a property with the name 'Description--£Rate'.
Thanks for any help,
Mark
You could create a new anonymous class using a simple LINQ projection, and then use the SelectList(IEnumerable, string, string) constructor overload to specify the value and text fields to be used for the <option> elements i.e.:
var stands =
db.Stands
.Where(s => s.ExhibitorID == null)
.Select(s => new
{
StandID = s.StandID,
Description = string.Format("{0}-- £{1}", s.Description, s.Rate)
})
.ToList();
ViewBag.StandID = new SelectList(stands, "StandID", "Description")
Edit
In C#6 and later, string interpolation makes for better reading than string.Format
...
Description = $"{s.Description}-- £{s.Rate}"
If you project to a strong ViewModel class name (instead of to an anonymous class), you will undoubtedly want to replace the magic strings with the safety of the nameof operator:
ViewBag.StandID = new SelectList(stands, nameof(Stand.StandID), nameof(Stand.Description));
var stands = db.Stands.Where(s => s.ExhibitorID == null).ToList();
IEnumerable<SelectListItem> selectList = from s in stands
select new SelectListItem
{
Value = s.StandID,
Text = s.Description + "-- £" + s.Rate.ToString()
};
ViewBag.StandID = new SelectList(selectList, "Value", "Text");
You can create a partial Model class
public partial class Stand
{
public string DisplayName
{
get
{
return this.Description + "-- £" + this.Rate.ToString();
}
}
}
Then in your View
var stands = db.Stands.Where(s => s.ExhibitorID == null).ToList();
ViewBag.StandID = new SelectList(stands,"StandID", "DisplayName");
The Format of the constructor that you are using is
SelectList(IEnumerable items, string dataValueField, string dataTextField).
So when you use it the way you have you are actually telling it to bind to the TextField called "Description-- £Rate" and if this is not what the field is called coming in the from the DB it won't know what you are indicating.
Either of the two methods described above will work as long as the value you have in your dataValueField matches the name of the property you put the Value in and the dataTextField matches the property name of where you put the Text, perhaps a mix of the two solutions above. (Only because I prefer lambda expressions over linq.) and using a selectlist item prevents it from have to do a ToList on the collection after the transform. you are actually creating the objects that naturally bind to a select list.
You also may want to put in checks on the description or rate to make sure they aren't empty before putting them into the list
var stands = db.Stands.Where(s => s.ExhibitorID == null)
.Select(s => new SelectListItem
{
Value = s.StandID.ToString(),
Text = s.Description + "-- £" + s.Rate.ToString()
});
ViewBag.StandID = new SelectList(stands, "Value", "Text");
I did this by modifying my View Model, here are my code:
The View Model
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using MvcEsosNew.Models;
using System.Web.Mvc;
namespace MvcEsosNew.ViewModels
{
public class EntitlementViewModel
{
public int EntitlementCount { get; set; }
public Entitlement Entitlement { get; set; }
public SelectList Member { get; set; }
public SelectList Job_Grade { get; set; }
public SelectList Department { get; set; }
public SelectList Esos_Batch { get; set; }
}
public class department_FullName
{
public int deptID { get; set; }
public string deptCode { get; set; }
public string deptName { get; set; }
public string fullName { get { return deptCode + " - " + deptName; } }
}
}
The Controller
public void getAllDepartment(EntitlementViewModel entitlementVM)
{
var department = from Department in db.Departments.Where(D => D.Status == "ACTIVE").ToList()
select new department_FullName
{
deptID = Department.id,
deptCode = Department.department_code,
deptName = Department.department_name
};
entitlementVM.Department = new SelectList(department, "deptID", "fullName");
}
The View
<div class="form-group row">
<div class="col-sm-2">
#Html.LabelFor(model => model.Entitlement.department_id)
</div>
<div class="col-sm-10">
#Html.DropDownListFor(model => model.Entitlement.department_id, Model.Department, new { #class="form-control" })
#Html.ValidationMessageFor(model => model.Entitlement.department_id)
</div>
</div>
The result:

How does Asp.net Core renders a view

How does MVC 6 renders a view. What's the actual method in Razor ViewEngine that generates the html output? Also if possible please explain the process of rendering a view.
May be you could point me to a file on mvc source on github. thanks!
Here is a complete solution of what you are looking for. I used dependency injection to get the HtmlHelper in the controller. You can inject your own helper if you want too.
using Microsoft.AspNet.Html.Abstractions;
using Microsoft.AspNet.Mvc;
using Microsoft.AspNet.Mvc.ModelBinding;
using Microsoft.AspNet.Mvc.Rendering;
using Microsoft.AspNet.Mvc.ViewEngines;
using Microsoft.AspNet.Mvc.ViewFeatures;
using Microsoft.AspNet.Mvc.ViewFeatures.Internal;
using Microsoft.Extensions.WebEncoders;
using System.ComponentModel.DataAnnotations;
using System;
public class MyController : Controller
{
private readonly IHtmlGenerator htmlGenerator;
ICompositeViewEngine viewEngine;
IModelMetadataProvider metadataProvider;
private readonly IHtmlHelper helper;
IHtmlEncoder htmlEncoder;
IUrlEncoder urlEncoder;
IJavaScriptStringEncoder javaScriptStringEncoder;
public MyController(IHtmlHelper helper, IHtmlGenerator htmlGenerator, ICompositeViewEngine viewEngine, IModelMetadataProvider metadataProvider, IHtmlEncoder htmlEncoder, IUrlEncoder urlEncoder, IJavaScriptStringEncoder javaScriptStringEncoder)
{
this.htmlGenerator = htmlGenerator;
this.viewEngine = viewEngine;
this.metadataProvider = metadataProvider;
this.htmlEncoder = htmlEncoder;
this.urlEncoder = urlEncoder;
this.javaScriptStringEncoder = javaScriptStringEncoder;
this.helper = helper;
}
[HttpGet]
public IActionResult MyHtmlGenerator()
{
MyViewModel temp = new MyViewModel();
var options = new HtmlHelperOptions();
options.ClientValidationEnabled = true;
ViewDataDictionary<MyViewModel> dic = new ViewDataDictionary<MyViewModel>(this.metadataProvider, new ModelStateDictionary());
ViewContext cc = new ViewContext(ActionContext, new FakeView(), dic, TempData, TextWriter.Null, options);
var type = typeof(MyViewModel);
var metadata = this.metadataProvider.GetMetadataForType(type);
ModelExplorer modelEx = new ModelExplorer(this.metadataProvider, metadata, temp);
ViewData["Description"] = "test desc";
ViewData["Id"] = 1;
this.ViewData = new ViewDataDictionary(this.metadataProvider, new ModelStateDictionary());
IHtmlHelper<MyViewModel> dd = new HtmlHelper<MyViewModel>(this.htmlGenerator, this.viewEngine, this.metadataProvider, this.htmlEncoder, this.urlEncoder, this.javaScriptStringEncoder);
((ICanHasViewContext)dd).Contextualize(cc);
dd.ViewContext.ViewData = this.ViewData;
var desc = GetString(dd.TextBoxFor(m => m.ID));
var ID = GetString(dd.TextBoxFor(m => m.Description));
// Do whatever you want with the ID and desc
return new ContentResult() { Content = ID + desc };
}
public static string GetString(IHtmlContent content)
{
var writer = new System.IO.StringWriter();
content.WriteTo(writer, new HtmlEncoder());
return writer.ToString();
}
}
public class MyViewModel : BaseAssetViewModel
{
// [RegularExpression(#"^-?\d{1,13}(\.\d{0,5})?$|^-?\.\d{1,5}$")]
[Required]
public int ID { get; set; }
[MinLength(2)]
public string Description { get; set; }
// Property with no validation
public string Other { get; set; }
}
public class FakeView : IView
{
string IView.Path
{
get
{
throw new NotImplementedException();
}
}
public Task RenderAsync(ViewContext viewContext)
{
throw new InvalidOperationException();
}
Task IView.RenderAsync(ViewContext context)
{
throw new NotImplementedException();
}
}
I don't know if this may be of help, may be you have to start to look at tag helpers:
https://github.com/DamianEdwards/TagHelperStarterWeb
they're working to a different way to create helpers that integrate in the page in a more natural way.

Html.ListBoxFor Object reference not set to an instance of an object Error

I am using view model to display a dropdownlist and i am also trying to get the value of the selected list, here is my view model
public class CreateJobViewModel
{
public int[] SelectedIndustriesIds { get; set; }
public IList<SelectListItem> IndustriesList { get; set; }
}
My controller
public ActionResult Create()
{
var industryList = repository.GetAllIndustries();
var model = new CreateJobViewModel
{
IndustriesList = industryList.Select(i => new SelectListItem
{
Value = i.IndustryId.ToString(),
Text = i.Name
}).ToList()
};
return View("~/Views/Dashboard/Job/Create.cshtml", model);
}
My post controller
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create(CreateJobViewModel model)
{
try
{
var job = new Job()
{
Title = "hi",
EmploymentHourId = 1,
LocationId = 1,
Salary = 50,
SalaryPeriodId = 1,
PostCode = 2131,
Role = "world",
Description = "hello",
IsPublished = false,
ShiftId = 1,
WorkDayId = 1,
NumberOfPosition = 5,
Meal = false,
SecondYearVisa = true,
Sponsorship = true,
Accommodation = true,
DurationId = 1,
IndustryExperiencePeriod = 5,
Id = User.Identity.GetUserId(),
};
foreach (int id in model.SelectedIndustriesIds)
{
var industry = repository.Industry(id);
job.Industries.Add(industry);
}
foreach (int id in model.SelectedSpecialRequirementsId)
{
var special = repository.SpecialRequirement(id);
job.SpecialRequirements.Add(special);
}
repository.AddJob(job);
return RedirectToAction("Create");
}
catch
{
return View("~/Views/Dashboard/Job/Create.cshtml");
}
}
Every time i try to submit the selected value, i get Object reference not set to an instance of an object Error on the following line in my view:
#model Taw.WebUI.Models.CreateJobViewModel
#Html.ListBoxFor(m => m.SelectedIndustriesIds, Model.IndustriesList) -- here i get the error
Any reason why?
When you submit the form your throwing an exception (confirmed in the comments) and in the catch block you are returning the view, which throws the exception you are seeing because Model.IndustriesList is null. You need to re-assign the value before you return the view.
Since you need to assign SelectLists in the GET method and in the POST method if you return the view, I tend to re-factor this to a separate method to keep the controller code a bit cleaner. Note the following code is based on your model property being public SelectList IndustriesList { get; set; } which is a bit simpler than building IList<SelectListItem>
private void ConfigureViewModel(CreateJobViewModel model)
{
var industryList = repository.GetAllIndustries();
model.IndustriesList = new SelectList(industryList, "IndustryId", "Name")
// any other common stuff
}
and then in the action methods
public ActionResult Create()
{
var model = new CreateJobViewModel();
ConfigureViewModel(model);
return View(model);
}
public ActionResult Create(CreateJobViewModel model)
{
try
{
....
}
catch
{
ConfigureViewModel(model);
return View(model);
}
}
Note its also good practice to test if the model is valid before attempting to save it
public ActionResult Create(CreateJobViewModel model)
{
if (!ModelState.IsValid)
{
ConfigureViewModel(model);
return View(model); // return the view so the user can correct validation errors
}
....

MVC 4 HTML is never decoded on POST

I am using a Kendo editor to create email templates and on POST, once a change to the template has been submitted, always renders in encoded HTML.
This is my razor code on the page:
#model Business.Models.Administration.EmailSetupViewModel
#using Kendo.Mvc.UI;
<h2>Application Stages Portal</h2>
<h4>Email Setup</h4>
#using (Html.BeginForm())
{
if (Model.EmailSaved)
{
<h2>
Email template saved</h2>
}
else
{
#* #Html.DisplayFor(m => m.EmailSavedMsg)*#
}
#Html.DropDownListFor(m => m.EmailTemplateToEdit, Model.EmailTemplatesList)
<input type="submit" name="setup" value="setup" />
if (Model.ShowEmailForm)
{
<div id="email-edit">
#Html.Label("Title")
#Html.TextBoxFor(m => m.EmailTitle, new { style = "width:200px" })
<br />
#(Html.Kendo().Editor()
.Name("editor")
.HtmlAttributes(new { style = "width: 600px;height:440px" })
.Value(#<text>
#Html.Raw(Model.EmailBody)
</text>))
</div>
<input type="submit" id="btnSaveTemplate" name="update" value="update" />
<h2>
Please note</h2>
<p>
The following items are <i>reserved and should not be changed, you may move them
to a different place within the message. </i>
<ul>
<li><*name*> e.g. Fred Flinstone </li>
<li><*membernumber*> e.g. 12345678 </li>
</ul>
</p>
}
}
And this is where the actual editor markup is on the page
#(Html.Kendo().Editor()
.Name("editor")
.HtmlAttributes(new { style = "width: 600px;height:440px" })
.Value(#<text>
#Html.Raw(Model.EmailBody)
</text>))
Model.EmailBody contains the actual string.
When I GET the page, it renders fine. But when I do POST it never decodes so the rendering is wrong. I don't want to see all the HTML tags but the actual formatting.
This is my Controller code:
#region Email template
[HttpGet]
public ActionResult EmailSetup()
{
ViewBag.DisplayName = StaticFunctions.GetDisplayName(this.User.Identity.Name);
EmailSetupViewModel model = new EmailSetupViewModel();
Business.Administration.Email Email = new Business.Administration.Email();
var list = Email.GetTemplateList();
model.EmailTemplatesList = list.OrderBy(o => o.Text).ToList();
return View(model);
}
[HttpPost]
public ActionResult EmailSetup(EmailSetupViewModel model, string value, string editor)
{
ViewBag.DisplayName = StaticFunctions.GetDisplayName(this.User.Identity.Name);
string body = HttpUtility.HtmlDecode(editor); //encode to db
if (Request["update"] != null)
{
Business.Administration.Email Email = new Business.Administration.Email();
model.EmailSaved = Email.SaveTemplate(model, body);
//ModelState.Clear(); // when doing POST - clearing the ModelState will prevent encode of HTML (Default behaviour). This isn't good long term solution.
if (model.EmailSaved)
{
model.EmailSavedMsg = "Template saved";
}
else
{
model.EmailSavedMsg = "Template couldn't be saved";
}
model.EmailTemplatesList = Email.GetTemplateList();
model = Email.GetTemplate(model);
model.EmailBody = HttpUtility.HtmlDecode(model.EmailBody);
return View(model);
}
else
{
Business.Administration.Email Email = new Business.Administration.Email();
model.EmailTemplatesList = Email.GetTemplateList();
model = Email.GetTemplate(model);
model.EmailBody = HttpUtility.HtmlDecode(model.EmailBody);
return View(model);
}
}
#endregion
This is my model, I am using [AllowHtml] attribute on the property.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Web.Mvc;
namespace Business.Models.Administration
{
public class EmailSetupViewModel
{
public EmailSetupViewModel()
{
this.EmailTemplatesList = new List<SelectListItem>();
}
public string EmailTemplateToEdit { get; set; }
public List<SelectListItem> EmailTemplatesList { get; set; }
public string EmailTitle { get; set; }
[AllowHtml]
public string EmailBody { get; set; }
public bool ShowEmailForm { get; set; }
public bool EmailSaved { get; set; }
public string EmailSavedMsg { get; set; }
}
}
Finally two screenshots, one on GET and one on POST.
I was using ModelState.Clear() as well but when I clicked back on the browser, it wouldn't decode.
So basically I want help rendering the HTML in my editor on post so it renders properly and doesn't show HTML tags in the editor.