I have a relatively simple form that's posting to a controller, yet the model is coming through as null every time and I don't understand why. I'm using Umbraco CMS (so please ignore any Umbraco references if they're not relevant) and jquery combined with an image plugin (Filepond) to post the data. This is my model:
public class PropertyViewModel: PublishedContentWrapped
{
public PropertyViewModel(IPublishedContent content, IPublishedValueFallback publishedValueFallback) : base(content, publishedValueFallback)
{
this.Countries = GetCountries();
}
[Required]
public string Country { get; set; }
[Required]
public string DisplayName { get; set; }
[Required]
public string Summary { get; set; }
[Required]
public string Description { get; set; }
[Required(ErrorMessage = "Please select at least one image")]
public List<IFormFile> Images { get; set; }
public SelectList Countries { get; set; }
public SelectList GetCountries()
{
List<string> countryNames = Bia.Countries.Iso3166.Countries.GetAllActiveDirectoryNames();
var countries = new List<SelectListItem>();
foreach (var country in countryNames)
countries.Add(new SelectListItem { Text = country, Value = country });
return new SelectList(countries, "Text", "Value");
}
}
Here is the controller:
[HttpPost]
[ValidateAntiForgeryToken]
[ValidateUmbracoFormRouteString]
public async Task<IActionResult> HandleAddProperty(PropertyViewModel model)
{
if (ModelState.IsValid == false)
{
return CurrentUmbracoPage();
}
// Carry on execution..
}
Finally, here is my view. I should mention that model is a new instance of the PropertyViewModel class:
#using (Html.BeginUmbracoForm<PropertySurfaceController>("HandleAddProperty", null, new { #id = "form-add" }))
{
<div asp-validation-summary="All" class="text-danger"></div>
<div class="mb-3">
<select asp-for="#Model.Country" asp-items="Model.Countries" class="form-select">
<option value="">Select country</option>
</select>
<span asp-validation-for="#Model.Country" class="form-text text-danger"></span>
</div>
<div class="mb-3">
<label asp-for="#Model.DisplayName" class="form-label"></label>
<input asp-for="#Model.DisplayName" class="form-control" aria-required="true" />
<span asp-validation-for="#Model.DisplayName" class="form-text text-danger"></span>
</div>
<div class="mb-3">
<label asp-for="#Model.Summary" class="form-label"></label>
<textarea asp-for="#Model.Summary" class="form-control" aria-required="true"></textarea>
<span asp-validation-for="#Model.Summary" class="form-text text-danger"></span>
</div>
<div class="mb-3">
<label asp-for="#Model.Description" class="form-label"></label>
<textarea asp-for="#Model.Description" class="form-control" aria-required="true"></textarea>
<span asp-validation-for="#Model.Description" class="form-text text-danger"></span>
</div>
<div class="mb-3">
<input type="file" class="filepond" id="file" name="filepond" multiple data-max-file-size="5MB" data-max-files="10" accept="image/png, image/jpeg, image/gif">
</div>
<button type="submit" class="btn btn-primary" id="submit">Submit</button>
}
<script>
$(document).ready(function(e){
FilePond.registerPlugin(
FilePondPluginImageResize,
FilePondPluginFileValidateSize
);
var pond = FilePond.create(
document.querySelector('#file'), {
allowMultiple: true,
instantUpload: false,
allowProcess: false,
imageResizeTargetWidth: 1280,
imageResizeUpscale: false,
imageResizeMode: 'contain'
});
$("#form-add").submit(function (e) {
e.preventDefault();
var formdata = new FormData(this);
// append FilePond files into the form data
var pondFiles = pond.getFiles();
for (var i = 0; i < pondFiles.length; i++) {
formdata.append('Images', pondFiles[i].file);
}
console.log('formdata', formdata);
$.ajax({
url: "/umbraco/surface/PropertySurface/HandleAddProperty",
data: formdata,
dataType: 'JSON',
processData: false,
contentType: false,
method:"post"
}).done(function (response) {
// todo
});
})
});
</script>
When I inspect in Chrome to see what data is being sent to the server I can see the following:
So I can see the fields are matching the model, yet it's still coming through as null. I can't figure out where it's failing.
I've tried using [FromBody] and [FromForm] with the controller parameter, but it made no difference.
Is anyone able to spot where I'm going wrong?
I think the second parameter in BeginUmbracoForm is supposed to be the model, not null. So:
#using (Html.BeginUmbracoForm<PropertySurfaceController>("HandleAddProperty", new { #id = "form-add" }))
{ ... }
Related
So, I have web application for currency exchange. What I need is to get the result of conversion without reloading the page. In this application I get data from api. After submiting the form page starts to reload and calls HttpGet method again. I get exception because of it. Is there any way to convert currency and do not reload the page?
Model
public class ExchangeRate
{
[JsonProperty("rates")]
public Dictionary<string, double> Rates { get; set; }
[JsonProperty("base")]
public string Base { get; set; }
[JsonProperty("date")]
public DateTimeOffset Date { get; set; }
}
Controller
public class HomeController : Controller
{
[HttpGet]
public async Task<ActionResult> Index()
{
ExchangeRate curencyInfo = new ExchangeRate();
using (HttpClient client = new HttpClient())
{
curencyInfo = await client.GetFromJsonAsync<ExchangeRate>("https://api.exchangeratesapi.io/latest?base=USD");
}
return View(curencyInfo);
}
[HttpPost]
public ActionResult Index(decimal fromCurrency, decimal toCurrency, decimal fromValue)
{
var toValue = toCurrency / fromCurrency * fromValue;
decimal result = Math.Round(toValue, 2);
ViewBag.toValue = result;
return View();
}
}
View
#model WebCurrencyConverter.Models.ExchangeRate
#{
ViewData["Title"] = "Currency Converter";
}
<h1>Currency Converter</h1>
<form method="post">
<select name="fromCurrency">
#foreach (var itemFrom in Model.Rates)
{
<option value="#itemFrom.Value">#itemFrom.Key</option>
}
</select>
<input type="hidden" value="#Model.Rates" />
<select name="toCurrency">
#foreach (var itemTo in Model.Rates)
{
<option value="#itemTo.Value">#itemTo.Key</option>
}
</select>
<input type="number" name="fromValue" step="any" />
<input type="number" name="toValue" value="#ViewBag.toValue" />
<input type="submit" class="btn btn-success" value="Convert" />
</form>
I get exception because of it. Is there any way to convert currency and do not reload the page?
To avoid reloading the page,I think you could use ajax to post data.
Here is a whole working demo:
View:
#model ExchangeRate
#{
ViewData["Title"] = "Currency Converter";
}
<h1>Currency Converter</h1>
<form method="post">
<select name="fromCurrency">
#foreach (var itemFrom in Model.Rates)
{
<option value="#itemFrom.Value">#itemFrom.Key</option>
}
</select>
<input type="hidden" value="#Model.Rates" />
<select name="toCurrency">
#foreach (var itemTo in Model.Rates)
{
<option value="#itemTo.Value">#itemTo.Key</option>
}
</select>
<input type="number" name="fromValue" step="any" />
<input type="number" name="toValue" /> //change here.....
//change the type to button and add the id for button
<input type="button" class="btn btn-success" id="convert" value="Convert" />
</form>
Js in View:
#section Scripts
{
<script>
$("#convert").click(function () {
console.log($('form').serialize());
$.ajax({
url: "/Home/Index",
type: "Post",
data: $('form').serialize(),
success: function (data) {
console.log(data.result);
$('input[name="toValue"]').val(data.result);
}
})
})
</script>
}
Controller:
[HttpGet]
public async Task<ActionResult> Index()
{
ExchangeRate curencyInfo = new ExchangeRate();
curencyInfo.Rates = new Dictionary<string, double>() { };
curencyInfo.Rates.Add("US", 1);
curencyInfo.Rates.Add("Euro", 0.82);
return View(curencyInfo);
}
[HttpPost]
public JsonResult Index(decimal fromCurrency, decimal toCurrency, decimal fromValue)
{
var toValue = toCurrency / fromCurrency * fromValue;
decimal result = Math.Round(toValue, 2);
return Json(new { result = result });
}
Result:
I have an app in which I post some JSON data to my server and then create an object that will later be added to a database.This is done by the "Save" method in my "SendItemsController":
[Route("SendItems/Save")]
[ApiController]
public class SendItemsController : Controller
{
private AppDbContext _db;
public SendItemsController(AppDbContext db)
{
_db = db;
}
[HttpPost]
[Consumes("application/json")]
public async Task<IActionResult> Save([FromBody] ShoppingCart s)
{
await _db.ShoppingCarts.AddAsync(s);
await _db.SaveChangesAsync();
return RedirectToAction("Index");
}
[HttpGet("~/ThankYou/Index")]
public IActionResult Index()
{
return View();
}
}
After the object is added to the database I try to redirect the client to a "ThankYou" page.
When first launched when I press my order button on the "Cart" page it redirects the client to the "ThankYou" page without firing the "Save" method, but if I go back from the "ThankYou" page to the "Cart" page and hit the button again, the method is fired, the object is added to the database and the client is redirected to the "ThankYou" page, just as it should.My question is what should I change to my code in order to make it fire from the first time, not after going back and hitting the order button again.
I will provide the code that I have written.
Here is the javascript that I use in order to form my JSON object and then POST it to my server:
var orderB = document.getElementById("orderB");
orderB.addEventListener("click", function () {
var inputName = document.getElementById("inputName").value;
var inputAddress = document.getElementById("inputAddress").value;
var inputMail = document.getElementById("inputMail").value;
var auxArray = [];
for (var i = 0; i < productsAux.length; i++) {
if (productsAux[i]!="") {
auxArray[i-1] = { "productName": productsAux[i].titlu, "productPrice": productsAux[i].pret, "quantity": localStorage.getItem(productsAux[i].titlu) };
}
}
var shoppingCart = {
productList: auxArray,
clientName: inputName,
clientAddress: inputAddress,
clientMail: inputMail
};
$.ajax({
type: "POST",
data: JSON.stringify(shoppingCart),
url: "senditems/save",
contentType: "application/json;charset=utf-8",
})
})
And here is the html for my form that I use to gather the name,address and email from the client :
<div class="form-group row">
<div class="col-4">
<label id="clientName"></label>
</div>
<div class="col-6">
<input id="inputName" type="text" />
</div>
</div>
<div class="form-group row">
<div class="col-4">
<label id="clientAddress"></label>
</div>
<div class="col-6">
<input id="inputAddress" type="text" />
</div>
</div>
<div class="form-group row">
<div class="col-4">
<label id="clientMail"></label>
</div>
<div class="col-6">
<input id="inputMail" type="text" />
</div>
</div>
<div class="form-group row">
<div class="col-3 offset-4">
<button class="btn btn-primary " id="orderB" asp-controller="SendItems" action="Save">ORDER</button>
</div>
</div>
</form>
Firstly,I am not sure if you have provided the whole ajax,because an ajax requested action will not let you redirect in the backend.You could only redirect it in your ajax function.
Secondly,you have added the onclick event,no need to add asp-controller tag helper it would render wrong formaction.
Thirdly,the item i starts with 0 but the index of auxArray starts with -1,the product array may not pass to the backend successfully.
At last,the ajax url should be:/senditems/save.
Here is a working demo like below:
Model:
public class ShoppingCart
{
public string clientName { get; set; }
public string clientAddress { get; set; }
public string clientMail { get; set; }
public List<Product> productList { get; set; }
}
public class Product
{
public string productName { get; set; }
public int productPrice { get; set; }
public int quantity { get; set; }
}
View:
<form>
//....
<div class="form-group row">
<div class="col-3 offset-4">
<button class="btn btn-primary" id="orderB" type="button">ORDER</button>
</div>
</div>
</form>
#section Scripts
{
<script>
var orderB = document.getElementById("orderB");
orderB.addEventListener("click", function () {
var inputName = document.getElementById("inputName").value;
var inputAddress = document.getElementById("inputAddress").value;
var inputMail = document.getElementById("inputMail").value;
var auxArray = [];
for (var i = 0; i < 1; i++) {
auxArray[i] = { "productName": "aaaa", "productPrice": 34, "quantity": 3 };
}
var shoppingCart = {
productList: auxArray,
clientName: inputName,
clientAddress: inputAddress,
clientMail: inputMail
};
$.ajax({
type: "POST",
data: JSON.stringify(shoppingCart),
url: "/senditems/save", //add `/`
contentType: "application/json;charset=utf-8",
success: function (res) {
window.location.href = res; //redirect like this
}
})
})
</script>
}
Controller:
[Route("SendItems/Save")]
[ApiController]
public class SendItemsController : Controller
{
[HttpPost]
[Consumes("application/json")]
public async Task<IActionResult> Save([FromBody] ShoppingCart s)
{
return Json("YourRedirectUrl");
}
}
Result:
I have a form to create new data entries for comments. Creating completely new entries works fine. However, when I have already created one entry for my entity I want to populate the data from the last entry in my form.
I have tried to modify the OnGet action to include the data from the last entry. I copied the OnGet code from the Edit view into the Create view. However, if I do this, the Create page is not displayed anymore.
I have the following model:
public class ProjectComment
{
public int Id { get; set; }
public int? ProjectId { get; set; }
public Project Project { get; set; }
public int RAGStatusId { get; set; }
public RAGStatus RAGStatus { get; set; }
public string StatusComment { get; set; }
public string EscalationComment { get; set; }
public string GeneralComment { get; set; }
public double? EOQ { get; set; }
public DateTime LastUpdateDate { get; set; }
public ProjectComment ()
{
this.LastUpdateDate = DateTime.UtcNow;
}
The create form Create.cshtml:
#page
#model SimpleProjectReporting.Pages.ClientDetails.CreateModel
#{
ViewData["Title"] = "Create";
}
<h1>Create</h1>
<h4>ProjectComment</h4>
<hr />
<div class="row">
<div class="col-md-4">
<form method="post">
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
<div class="form-group">
<label asp-for="ProjectComment.ProjectId" class="control-label"></label>
<select asp-for="ProjectComment.ProjectId" class="form-control" asp-items="ViewBag.ProjectId"><option value="" default="" selected="">-- Select --</option></select>
</div>
<div class="form-group">
<label asp-for="ProjectComment.RAGStatusId" class="control-label"></label>
<select asp-for="ProjectComment.RAGStatusId" class="form-control" asp-items="ViewBag.RAGStatusId"><option value="" default="" selected="">-- Select --</option></select>
</div>
<div class="form-group">
<label asp-for="ProjectComment.StatusComment" class="control-label"></label>
<input asp-for="ProjectComment.StatusComment" class="form-control" />
<span asp-validation-for="ProjectComment.StatusComment" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="ProjectComment.EOQ" class="control-label"></label>
<input asp-for="ProjectComment.EOQ" class="form-control" />
<span asp-validation-for="ProjectComment.EOQ" class="text-danger"></span>
</div>
<div class="form-group">
<input type="submit" value="Create" class="btn btn-primary" />
</div>
</form>
</div>
</div>
The original Create.cshtml.cs action:
[BindProperty]
public ProjectComment ProjectComment { get; set; }
public IActionResult OnGet()
{
ViewData["ProjectId"] = new SelectList(_context.Project.Where(a => a.IsArchived == false), "Id", "ProjectName");
ViewData["RAGStatusId"] = new SelectList(_context.RAGStatus.Where(a => a.IsActive == true), "Id", "RAGStatusName");
return Page();
}
// To protect from overposting attacks, please enable the specific properties you want to bind to, for
// more details see https://aka.ms/RazorPagesCRUD.
public async Task<IActionResult> OnPostAsync()
{
if (!ModelState.IsValid)
{
return Page();
}
_context.ProjectComment.Add(ProjectComment);
await _context.SaveChangesAsync();
return RedirectToPage("./Index");
}
The modified Create.cshtml.cs OnGet action:
public async Task<IActionResult> OnGetAsync(int? id)
{
if (id == null)
{
return NotFound();
}
ProjectComment = await _context.ProjectComment
.Include(p => p.Project)
.Include(p => p.RAGStatus).FirstOrDefaultAsync(m => m.Id == id);
if (ProjectComment == null)
{
return NotFound();
}
When modifying the action the way I did it, the page is not displayed anymore (404 error).
I would like to populate the create form with the data from the last entry in the database. If there is no comment, the create page would only populate the name of the project.
You are not sending the "id" parameter to your post action I guess.
So could you please try to adding this line under your form tag:
<form method="post">
<input type="hidden" id="ProjectComment.Id" name="id" value="ProjectComment.Id" />
You are trying to reach the last record of your ProjectComment table.
There are more than one methods to find the last record of your data table. But lets keep it simple.
You have an integer based identity column, which is Auto Increment. So you can simply use below methods to reach out the last created data of your table.
In your OnGetAsync() method:
//maxId will be the maximum value of "Id" columns. Which means that the maximum value is the last recorded value.
int maxId = _context.ProjectComment.Max(i => i.Id);
//And this line will bring you the last recorded "ProjectComment" object.
var projectComment = _context.ProjectComment.Find(maxId);
//You can assign it to your above 'ProjectComment' property if you want to..
ProjectComment = projectComment
Now, since you've find the last recorded data in your database, you can use that object.
Firstly, thanks to Burak for providing the above solution, which works when you want to display the last row in the table. This helped me solving my issue by using the same approach and finding the record based on the Id of the record.
I have amended the code from the Create.cshtml.cs file as follows:
public async Task<IActionResult> OnGetAsync(int? id, int projectid)
{
//This will find the "ProjectComment" object.
var projectComment = _context.ProjectComment.Find(id);
//This will display the 'ProjectComment' on the page
ProjectComment = projectComment;
if (id == null)
{
ProjectComment = projectComment;
ViewData["ProjectId"] = new SelectList(_context.Project, "Id", "ProjectName", projectid);
return Page();
}
ViewData["ProjectId"] = new SelectList(_context.Project, "Id", "ProjectName");
return Page();
}
I am using the int projectid to populate the drop down menu of the project when there is no comment create yet.
I have a models like this:
public class Feature
{
public int id { get; set; }
public List<keyFeatures> features { get; set; }
}
public class keyFeatures
{
public string key { get; set; }
public string value { get; set; }
}
In the Front I dynamically create key pair values with angular
<div ng-repeat="item in items" class="row">
<div name="features" class="row">
<div class="col-md-6">
<input type="text" class="form-control input-md" placeholder="{{item.key}}" name="key" ng-model="item.key" />
<span class="error" ng-show="$parent.submitted&& innerForm.fieldU.$error.required">Required!</span>
</div>
<div class="col-md-6">
<input type="text" class="form-control input-md" name="value" placeholder="enter text..." ng-model="item.value" />
<span class="error" ng-show="$parent.submitted && innerForm.userName.$error.required"> Required! </span>
</div>
</div>
</div>
Iv tried different ways but it always return null to my controller on post (Razor)
public ActionResult Create( Feature model)
{
// ...
}
Thanks in advance
You could use the Angular $http service in order to make an asynchronous call to your MVC controller and submit the desired data.
var feature = {
id: 123,
features: $scope.items
};
$http.post(url, JSON.stringify(feature)).success(function(result) {
alert('successfully sent to server');
}).error(function() {
alert('an error occurred');
});
In this example I assume that $scope.items is an array of objects having the key and value properties. For example:
$scope.items = [
{ key: 'key1', value: 'value1' },
{ key: 'key2', value: 'value2' },
{ key: 'key3', value: 'value3' }
];
I'm rather new to the ASP.net MVC world and I'm trying to figure out how to render a group of checkboxes that are strongly typed to a view model. In webforms I would just use the checkboxlist control but im a bit lost with MVC.
I'm building a simple contact form for a wedding planning business and need to pass whatever checkbox values the user selects to my controller.
The form checkboxes need to look like this:
Your help would be greatly appreciated. Thanks!
Here's what I have so far.
CONTROLLER
[HttpPost]
public ActionResult Contact(ContactViewModel ContactVM)
{
if (!ModelState.IsValid)
{
return View(ContactVM);
}
else
{
//Send email logic
return RedirectToAction("ContactConfirm");
}
}
VIEW MODEL
public class ContactViewModel
{
[Required]
public string Name { get; set; }
[Required]
public string Phone { get; set; }
[Required]
[DataType(DataType.EmailAddress)]
public string Email { get; set; }
[Required]
public string Subject { get; set; }
public IEnumerable<SelectListItem> SubjectValues
{
get
{
return new[]
{
new SelectListItem { Value = "General Inquiry", Text = "General Inquiry" },
new SelectListItem { Value = "Full Wedding Package", Text = "Full Wedding Package" },
new SelectListItem { Value = "Day of Wedding", Text = "Day of Wedding" },
new SelectListItem { Value = "Hourly Consultation", Text = "Hourly Consultation" }
};
}
}
//Not sure what I should do for checkboxes...
}
VIEW
#model NBP.ViewModels.ContactViewModel
#{
ViewBag.Title = "Contact";
Layout = "~/Views/Shared/_Layout.cshtml";
}
#using (Html.BeginForm())
{
<div id="ContactContainer">
<div><span class="RequiredField">* </span>Your Name:</div>
<div>
#Html.TextBoxFor(model => model.Name)
</div>
<div><span class="RequiredField">* </span>Your Phone:</div>
<div>
#Html.TextBoxFor(model => model.Phone)
</div>
<div><span class="RequiredField">* </span>Your Email:</div>
<div>
#Html.TextBoxFor(model => model.Email)
</div>
<div>Subject:</div>
<div>
#Html.DropDownListFor(model => model.Subject, Model.SubjectValues)
</div>
<div>Vendor Assistance:</div>
<div>
<!-- CHECKBOXES HERE -->
</div>
<div>
<input id="btnSubmit" type="submit" value="Submit" />
</div>
</div>
}
You could enrich your view model:
public class VendorAssistanceViewModel
{
public string Name { get; set; }
public bool Checked { get; set; }
}
public class ContactViewModel
{
public ContactViewModel()
{
VendorAssistances = new[]
{
new VendorAssistanceViewModel { Name = "DJ/BAND" },
new VendorAssistanceViewModel { Name = "Officiant" },
new VendorAssistanceViewModel { Name = "Florist" },
new VendorAssistanceViewModel { Name = "Photographer" },
new VendorAssistanceViewModel { Name = "Videographer" },
new VendorAssistanceViewModel { Name = "Transportation" },
}.ToList();
}
[Required]
public string Name { get; set; }
[Required]
public string Phone { get; set; }
[Required]
[DataType(DataType.EmailAddress)]
public string Email { get; set; }
[Required]
public string Subject { get; set; }
public IEnumerable<SelectListItem> SubjectValues
{
get
{
return new[]
{
new SelectListItem { Value = "General Inquiry", Text = "General Inquiry" },
new SelectListItem { Value = "Full Wedding Package", Text = "Full Wedding Package" },
new SelectListItem { Value = "Day of Wedding", Text = "Day of Wedding" },
new SelectListItem { Value = "Hourly Consultation", Text = "Hourly Consultation" }
};
}
}
public IList<VendorAssistanceViewModel> VendorAssistances { get; set; }
}
Controller:
public class HomeController : Controller
{
public ActionResult Index()
{
return View(new ContactViewModel());
}
[HttpPost]
public ActionResult Index(ContactViewModel model)
{
if (!ModelState.IsValid)
{
return View(model);
}
//Send email logic
return RedirectToAction("ContactConfirm");
}
}
View:
#using (Html.BeginForm())
{
<div id="ContactContainer">
<div><span class="RequiredField">* </span>Your Name:</div>
<div>
#Html.TextBoxFor(model => model.Name)
</div>
<div><span class="RequiredField">* </span>Your Phone:</div>
<div>
#Html.TextBoxFor(model => model.Phone)
</div>
<div><span class="RequiredField">* </span>Your Email:</div>
<div>
#Html.TextBoxFor(model => model.Email)
</div>
<div>Subject:</div>
<div>
#Html.DropDownListFor(model => model.Subject, Model.SubjectValues)
</div>
<div>Vendor Assistance:</div>
<div>
#for (int i = 0; i < Model.VendorAssistances.Count; i++)
{
<div>
#Html.HiddenFor(x => x.VendorAssistances[i].Name)
#Html.CheckBoxFor(x => x.VendorAssistances[i].Checked)
#Html.LabelFor(x => x.VendorAssistances[i].Checked, Model.VendorAssistances[i].Name)
</div>
}
</div>
<div>
<input id="btnSubmit" type="submit" value="Submit" />
</div>
</div>
}
Use a string array in your view model. You can then use the helper I hacked together. if you don't want to use the helper and the enum then see the actual Html at the bottom. The binder will return a string array with only the selected string values in it. if none are selected it returns a null value for your array. You must account for that, you have been warned :)
View Model:
[Display(Name = "Which Credit Cards are Accepted:")]
public string[] CreditCards { get; set; }
Helper:
public static HtmlString CheckboxGroup<TModel, TProperty>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TProperty>> propertySelector, Type EnumType)
{
var groupName = GetPropertyName(propertySelector);
var modelValues = ModelMetadata.FromLambdaExpression(propertySelector, htmlHelper.ViewData).Model;//propertySelector.Compile().Invoke(htmlHelper.ViewData.Model);
StringBuilder literal = new StringBuilder();
foreach (var value in Enum.GetValues(EnumType))
{
var svalue = value.ToString();
var builder = new TagBuilder("input");
builder.GenerateId(groupName);
builder.Attributes.Add("type", "checkbox");
builder.Attributes.Add("name", groupName);
builder.Attributes.Add("value", svalue);
var contextValues = HttpContext.Current.Request.Form.GetValues(groupName);
if ((contextValues != null && contextValues.Contains(svalue)) || (modelValues != null && modelValues.ToString().Contains(svalue)))
{
builder.Attributes.Add("checked", null);
}
literal.Append(String.Format("</br>{1} <span>{0}</span>", svalue.Replace('_', ' '),builder.ToString(TagRenderMode.Normal)));
}
return (HtmlString)htmlHelper.Raw(literal.ToString());
}
private static string GetPropertyName<T, TProperty>(Expression<Func<T, TProperty>> propertySelector)
{
var body = propertySelector.Body.ToString();
var firstIndex = body.IndexOf('.') + 1;
return body.Substring(firstIndex);
}
HTML:
#Html.CheckboxGroup(m => m.CreditCards, typeof(VendorCertification.Enums.CreditCardTypes))
Use this if helper extensions scare you:
<input id="CreditCards" name="CreditCards" type="checkbox" value="Visa"
#(Model.CreditCards != null && Model.CreditCards.Contains("Visa") ? "checked=true" : string.Empty)/>
<span>Visa</span><br />
<input id="CreditCards" name="CreditCards" type="checkbox" value="MasterCard"
#(Model.CreditCards != null && Model.CreditCards.Contains("MasterCard") ? "checked=true" : string.Empty)/>
<span>MasterCard</span><br />
For me this works too, and I think this is the simplest (reading the previous answers).
The viewmodel has a string[] for the check boxes.
public string[] Set { get; set; }
The view has this code, and you can repeat the input as many times you need. name, id of the input control has to match the name of the property of the viewmodel.
<div class="col-md-3">
<div class="panel panel-default panel-srcbox">
<div class="panel-heading">
<h3 class="panel-title">Set</h3>
</div>
<div class="panel-body">
<div class="form-group-sm">
<label class="control-label col-xs-3">1</label>
<div class="col-sm-8">
<input type="checkbox" id="Set" name="Set" value="1" />
</div>
<label class="control-label col-xs-3">2</label>
<div class="col-sm-8">
<input type="checkbox" id="Set" name="Set" value="2" />
</div>
</div>
</div>
</div>
</div>
On the post method the Set variable is an array, having the checked value(s).