MVC HtmlHelpers trouble with Razor - asp.net-mvc

I'm working on a non-profit donation platform and I'm using MVC for the first time. I've got the hang of it for the most part but right now I'm having a problem that I dont know how to address. I'm using the AthorizeNet.Helpers class and when I add the code from the expamples, it works for the most part except for it takes the form and puts it ABOVE the tag however it puts the form fields in the correct place. I'm trying to figure out how to render the tag in the correct place.
#using AuthorizeNet.Helpers;
#using (Html.BeginSIMForm("http://127.0.0.1:4768", 1.99M, "xxx_key", "yyy_key", true))
{
#Html.Raw(Html.CheckoutFormInputs(true))
#Html.Hidden("order_id", "1234")
<input type = "submit" value = "Pay" />
}
This is how it looks in HTML output:
<form action = 'https://test.authorize.net/gateway/transact.dll' method = 'post'>
<input type = 'hidden' name = 'x_fp_hash' value = '6bef386eaa89944efd62b47b86042910' \>
<input type = 'hidden' name = 'x_fp_sequence' value = '117'\>
<input type = 'hidden' name = 'x_fp_timestamp' value = 'xxx_key' \>
<input type = 'hidden' name = 'x_login' value = 'yyy_key' \>
<input type = 'hidden' name = 'x_amount' value = '1.99' \>
<input type = 'hidden' name = 'x_relay_url' value = 'http://127.0.0.1:4768' \>
<input type = 'hidden' name = 'x_relay_response' value = 'TRUE' \>
</form><!DOCTYPE html>
<html>
<body>
<h2>Payment Information</h2>
<div style = 'border: 1px solid #990000; padding:12px; margin-bottom:24px; background-color:#ffffcc;width:300px'>Test Mode</div>
<div style = 'float:left;width:250px;'>
<label>Credit Card Number</label>
<div id = 'CreditCardNumber'>
<input type = 'text' size = '28' name = 'x_card_num' value = '4111111111111111' id = 'x_card_num'/>
</div>
</div>
<div style = 'float:left;width:70px;'>
<label>Exp.</label>
<div id = 'CreditCardExpiration'>
<input type = 'text' size = '5' maxlength = '5' name = 'x_exp_date' value = '0116' id = 'x_exp_date'/>
</div>
</div>
<div style = 'float:left;width:70px;'>
<label>CCV</label>
<div id = 'CCV'>
<input type = 'text' size = '5' maxlength = '5' name = 'x_card_code' id = 'x_card_code' value = '123' />
</div>
</div><input id="order_id" name="order_id" type="hidden" value="1234" /> <input type = "submit" value = "Pay" />

This is how I fixed it.
As you stated, add #Html.Raw() around Html.CheckoutFormInputs(true)
The other change to make is in
namespace AuthorizeNet.Helpers -> CheckoutFormBuilders.cs
add a using of
using System.IO;
Change
HttpResponseBase to TextWriter
I did this in three spots.
HttpResponseBase _response; to TextWriter _response;
public SIMForm(TextWriter response, string returnUrl, decimal amount,
string apiLogin, string transactionKey)
:this(response,returnUrl,amount,apiLogin,transactionKey,true){}
public SIMForm(TextWriter response, string returnUrl, decimal amount,
string apiLogin, string transactionKey, bool isTest) {
_response = response;
_amount = amount;
_apiLogin = apiLogin;
_transactionkey = transactionKey;
_returnUrl = returnUrl;
_isTest = isTest;
OpenForm();
}
Two more changes left
As directed in tpeczek answer, you need to change
helper.ViewContext.HttpContext.Response
to
helper.ViewContext.Writer
This will look like
public static SIMForm BeginSIMForm(this HtmlHelper helper, string returnUrl,
decimal amount, string apiLogin,
string transactionKey) {
return new SIMForm(helper.ViewContext.Writer,
returnUrl,amount,apiLogin,
transactionKey,true);}
public static SIMForm BeginSIMForm(this HtmlHelper helper, string returnUrl,
decimal amount, string apiLogin,
string transactionKey, bool isTest) {
return new SIMForm(helper.ViewContext.Writer,
returnUrl, amount, apiLogin,
transactionKey,isTest);}

This kind of problem usually happens when a helper which is writing directly to output stream was written for ASP.NET MVC 1 (and helpers that are enclosed in using are writing directly to output stream most of the time). In ASP.NET MVC 1 you could write to output stream by using this:
htmlHelper.ViewContext.HttpContext.Response.Output
In later ASP.NET MVC version you should be using this:
htmlHelper.ViewContext.Writer
That ensures Razor compatibility. If you have access to AuthorizeNet.Helpers source code you can fix it by yourself, if you don't than you have to contact authors for fixing it.

It's possible that the helper from AuthorizeNet is not written correctly to work with Razor. Without actually looking at the source of their assembly it's hard to say if that's the case. You might want to try to get in touch with their customer support.

Using the example from EpicThreeDev above I was able to get the form html to appear correctly. The Pay button however, was juxtaposed above the ccv field, so in order to fix that on the Index.cshtml code base I changed the HTML to
<br />
<input type = "submit" value = "Pay" style="position:relative; top:35px;" />
once I did that I was able to post the form, however after doing that I got the error that is addressed in post.
http://community.developer.authorize.net/t5/Integration-and-Testing/Having-Trouble-setting-up-MVC3-application-with-DPM/m-p/13226

Here's my solution. It just prints the Authorize.net hidden fields, so you can write your own form tag. Fill in your web.config with the relevant AppSettings values. This way you can easily change between development and production.
public static class MyAuthNetHelper
{
public const string TEST_URL = "https://test.authorize.net/gateway/transact.dll";
public const string LIVE_URL = "https://secure.authorize.net/gateway/transact.dll";
public static string ApiLogin
{
get { return ConfigurationManager.AppSettings["AuthNetAPILogin"]; }
}
public static string TransactionKey
{
get { return ConfigurationManager.AppSettings["AuthNetAPITransactionKey"]; }
}
public static string ReturnUrl
{
get { return ConfigurationManager.AppSettings["AuthNetReturnUrl"]; }
}
public static string ThanksUrl
{
get { return ConfigurationManager.AppSettings["AuthNetThanksUrl"]; }
}
public static bool TestMode
{
get { return bool.Parse(ConfigurationManager.AppSettings["AuthNetTestMode"]); }
}
public static string GatewayUrl
{
get { return TestMode ? TEST_URL : LIVE_URL; }
}
public static MvcHtmlString AuthNetDirectPostFields(this HtmlHelper helper, decimal amount)
{
var seq = Crypto.GenerateSequence();
var stamp = Crypto.GenerateTimestamp();
var fingerPrint = Crypto.GenerateFingerprint(TransactionKey,
ApiLogin, amount, seq.ToString(), stamp.ToString());
var str = new StringBuilder();
str.Append(helper.Hidden("x_fp_hash", fingerPrint));
str.Append(helper.Hidden("x_fp_sequence", seq));
str.Append(helper.Hidden("x_fp_timestamp", stamp));
str.Append(helper.Hidden("x_login", ApiLogin));
str.Append(helper.Hidden("x_amount", amount));
str.Append(helper.Hidden("x_relay_url", ReturnUrl));
str.Append(helper.Hidden("x_relay_response", "TRUE"));
return MvcHtmlString.Create(str.ToString());
}
}
cshtml:
<form id="paymentForm" method="POST" action="#MyAuthNetHelper.GatewayUrl" class="form-horizontal offset1">
#Html.AuthNetDirectPostFields(PutYourAmountHere)
...
</form>
ThanksUrl is used in your action to handle the response from Authorize.net. Check this for more: http://developer.authorize.net/integration/fifteenminutes/csharp/

Related

Creating dynamic forms with .net.core

I have a requirement to have different forms for different clients which can all be configured in the background (in the end in a database)
My initial idea is to create an object for "Form" which has a "Dictionary of FormItem" to describe the form fields.
I can then new up a dynamic form by doing the following (this would come from the database / service):
private Form GetFormData()
{
var dict = new Dictionary<string, FormItem>();
dict.Add("FirstName", new FormItem()
{
FieldType = Core.Web.FieldType.TextBox,
FieldName = "FirstName",
Label = "FieldFirstNameLabel",
Value = "FName"
});
dict.Add("LastName", new FormItem()
{
FieldType = Core.Web.FieldType.TextBox,
FieldName = "LastName",
Label = "FieldLastNameLabel",
Value = "LName"
});
dict.Add("Submit", new FormItem()
{
FieldType = Core.Web.FieldType.Submit,
FieldName = "Submit",
Label = null,
Value = "Submit"
});
var form = new Form()
{
Method = "Post",
Action = "Index",
FormItems = dict
};
return form;
}
Inside my Controller I can get the form data and pass that into the view
public IActionResult Index()
{
var formSetup = GetFormData(); // This will call into the service and get the form and the values
return View(formSetup);
}
Inside the view I call out to a HtmlHelper for each of the FormItems
#model Form
#addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
#using FormsSpike.Core.Web
#{
ViewData["Title"] = "Home Page";
}
#using (Html.BeginForm(Model.Action, "Home", FormMethod.Post))
{
foreach (var item in Model.FormItems)
{
#Html.FieldFor(item);
}
}
Then when posting back I have to loop through the form variables and match them up again. This feels very old school I would expect would be done in a model binder of some sort.
[HttpPost]
public IActionResult Index(IFormCollection form)
{
var formSetup = GetFormData();
foreach (var formitem in form)
{
var submittedformItem = formitem;
if (formSetup.FormItems.Any(w => w.Key == submittedformItem.Key))
{
FormItem formItemTemp = formSetup.FormItems.Single(w => w.Key == submittedformItem.Key).Value;
formItemTemp.Value = submittedformItem.Value;
}
}
return View("Index", formSetup);
}
This I can then run through some mapping which would update the database in the background.
My problem is that this just feels wrong :o{
Also I have used a very simple HtmlHelper but I can't really use the standard htmlHelpers (such as LabelFor) to create the forms as there is no model to bind to..
public static HtmlString FieldFor(this IHtmlHelper html, KeyValuePair<string, FormItem> item)
{
string stringformat = "";
switch (item.Value.FieldType)
{
case FieldType.TextBox:
stringformat = $"<div class='formItem'><label for='item.Key'>{item.Value.Label}</label><input type='text' id='{item.Key}' name='{item.Key}' value='{item.Value.Value}' /></ div >";
break;
case FieldType.Number:
stringformat = $"<div class='formItem'><label for='item.Key'>{item.Value.Label}</label><input type='number' id='{item.Key}' name='{item.Key}' value='{item.Value.Value}' /></ div >";
break;
case FieldType.Submit:
stringformat = $"<input type='submit' name='{item.Key}' value='{item.Value.Value}'>";
break;
default:
break;
}
return new HtmlString(stringformat);
}
Also the validation will not work as the attributes (for example RequiredAttribute for RegExAttribute) are not there.
Am I having the wrong approach to this or is there a more defined way to complete forms like this?
Is there a way to create a dynamic ViewModel which could be created from the origional setup and still keep all the MVC richness?
You can do this using my FormFactory library.
By default it reflects against a view model to produce a PropertyVm[] array:
```
var vm = new MyFormViewModel
{
OperatingSystem = "IOS",
OperatingSystem_choices = new[]{"IOS", "Android",};
};
Html.PropertiesFor(vm).Render(Html);
```
but you can also create the properties programatically, so you could load settings from a database then create PropertyVm.
This is a snippet from a Linqpad script.
```
//import-package FormFactory
//import-package FormFactory.RazorGenerator
void Main()
{
var properties = new[]{
new PropertyVm(typeof(string), "username"){
DisplayName = "Username",
NotOptional = true,
},
new PropertyVm(typeof(string), "password"){
DisplayName = "Password",
NotOptional = true,
GetCustomAttributes = () => new object[]{ new DataTypeAttribute(DataType.Password) }
}
};
var html = FormFactory.RazorEngine.PropertyRenderExtension.Render(properties, new FormFactory.RazorEngine.RazorTemplateHtmlHelper());
Util.RawHtml(html.ToEncodedString()).Dump(); //Renders html for a username and password field.
}
```
Theres a demo site with examples of the various features you can set up (e.g. nested collections, autocomplete, datepickers etc.)
I'm going to put my solution here since I found this searching 'how to create a dynamic form in mvc core.' I did not want to use a 3rd party library.
Model:
public class IndexViewModel
{
public Dictionary<int, DetailTemplateItem> FormBody { get; set; }
public string EmailAddress { get; set; }
public string templateName { get; set; }
}
cshtml
<form asp-action="ProcessResultsDetails" asp-controller="home" method="post">
<div class="form-group">
<label asp-for=#Model.EmailAddress class="control-label"></label>
<input asp-for=#Model.EmailAddress class="form-control" />
</div>
#foreach (var key in Model.FormBody.Keys)
{
<div class="form-group">
<label asp-for="#Model.FormBody[key].Name" class="control-label">#Model.FormBody[key].Name</label>
<input asp-for="#Model.FormBody[key].Value" class="form-control" value="#Model.FormBody[key].Value"/>
<input type="hidden" asp-for="#Model.FormBody[key].Name"/>
</div>
}
<input type="hidden" asp-for="templateName" />
<div class="form-group">
<input type="submit" value="Save" class="btn btn-primary" />
</div>
</form>
You can use JJMasterData, it can create dynamic forms from your tables at runtime or compile time. Supports both .NET 6 and .NET Framework 4.8.
After setting up the package, access /en-us/DataDictionary in your browser
Create a Data Dictionary adding your table name
Click on More, Get Scripts, Execute Stored Procedures and then click on Preview and check it out
To use your CRUD at runtime, go to en-us/MasterData/Form/Render/{YOUR_DICTIONARY}
To use your CRUD at a specific page or customize at compile time, follow the example below:
At your Controller:
public IActionResult Index(string dictionaryName)
{
var form = new JJFormView("YourDataDictionary");
form.FormElement.Title = "Example of compile time customization"
var runtimeField = new FormElementField();
runtimeField.Label = "Field Label";
runtimeField.Name = "FieldName";
runtimeField.DataType = FieldType.Text;
runtimeField.VisibleExpression = "exp:{pagestate}='INSERT'";
runtimeField.Component = FormComponent.Text;
runtimeField.DataBehavior = FieldBehavior.Virtual; //Virtual means the field does not exist in the database.
runtimeField.CssClass = "col-sm-4";
form.FormElement.Fields.Add(runtimeField);
return View(form);
}
At your View:
#using JJMasterData.Web.Extensions
#model JJFormView
#using (Html.BeginForm())
{
#Model.GetHtmlString()
}

My viewdata spits out HTML,

For some reason my ViewData outputs HTML code even though I don't want it do.
This is what it
<br />2015-04-01
<br />2015-04-02
<br />2015-04-07
<br />2015-04-08
<br />2015-04-09
<br />2015-04-10
but I just want it to look like this
Du har ej rapporterat tid följande dagar:
2015-04-01
2015-04-02
2015-04-07
2015-04-08
2015-04-09
2015-04-10
This is part of my controller:
var missingdays = new DatabaseLayer().GetConsultantMissingDays(Constants.CurrentUser(User.Identity.Name));
if (missingdays.Count == 0)
{
ViewData["missingDays"] = "";
}
else
ViewData["missingDays"] = "Du har ej rapporterat tid följande dagar:<br />" +
string.Join("<br />", missingdays.Select(x => x.ToMissingDateJavascript()));
ViewData.Model = projectData;
return View();
}
And this is from my view:
<div>
#ViewData["missingDays"]
#Html.ValidationSummary()
</div>
and my Extensions
public static string ToMissingDateJavascript(this DateTime value) {
string dateString = value.ToString("yyyy-MM-dd");
return "" + dateString + "";
}
public static bool IsWeekend(this DateTime value) {
return value.DayOfWeek == DayOfWeek.Saturday || value.DayOfWeek == DayOfWeek.Sunday;
}
But I see the HTML code in the browser
You can wrap the call in Html.Raw(), like this:
#Html.Raw(ViewData["missingDays"])
However, it is better to pass in an array rather than HTML (or even passing it in the view model). You should avoid using any HTML in your controller as much as possible. For example:
#foreach(var date in (List<DateTime>)ViewData["stuff"])
{
<a href="javascript:SetDate('#date.ToString("yyyy-MM-dd")');">
#date.ToString("yyyy-MM-dd")
</a>
<br/>
}
Noet: I would also suggest not using br tags here and format with CSS.
In MVC the View is supposed to execute all work related to generating HTML, not the Controller. You can rewrite and simplify both the view and the controller as follows:
View:
#foreach(var date in ViewBag.MissingDays){
var isoDate=date.ToString("yyyy-MM-dd");
<br/>#isoDate
}
Controller:
//Assuming that missingdays is a List<DateTime> or other IEnumerable<DateTime>
ViewBag.MissingDays=missingdays;
You'll need to convert the string to HTML:
#{
string missingDays = ViewData["missingDays"]
}
#MvcHtmlString.Create(missingDays)

Radio Button Enum Helper for model with resource file

Problem:
Need to bind a strongly typed model which has a Gender as enum property. Also i like to show a Display text from a Resource file.
My Model is
public enum GenderViewModel
{
[Display(Name = "Male", ResourceType = typeof(Resources.Global), Order = 0)]
Male,
[Display(Name = "Female", ResourceType = typeof(Resources.Global), Order = 1)]
Female
}
Initially, I tried following http://romikoderbynew.com/2012/02/23/asp-net-mvc-rendering-enum-dropdownlists-radio-buttons-and-listboxes/
But it was bit complex and i was unable to correct my HTML however i want.
Then i had a look of simple and easy implementation from stackoverflow, pass enum to html.radiobuttonfor MVC3
and used a HtmlHelper in cshtml like below
#Html.RadioButtonForEnum(m => m.Gender)
HTML Produced
<label for="_Gender_Male">
<input type="radio" value="Male" name="Gender" id="_Gender_Male"
data-val-required="Gender is required" data-val="true" checked="checked">
<span class="radiotext">Male</span>
</label>
<label for="_Gender_Female">
<input type="radio" value="Female" name="Gender" id="_Gender_Female">
<span class="radiotext">Female</span></label>
It really simple and works well for me. But i am not getting values
from Resource files. My application is multilingual and I use a Global
Resource file for different language support.
Issue:
Male displayed should be Man and Female displayed should be Kvinna should be from Resource file, as my current culture is sv-se
Could any one help/ provide a simple solution which has a good control over HTML?
All you have to do is adapt my original helper so that it takes into account the DisplayAttribute:
public static class HtmlExtensions
{
public static MvcHtmlString RadioButtonForEnum<TModel, TProperty>(
this HtmlHelper<TModel> htmlHelper,
Expression<Func<TModel, TProperty>> expression
)
{
var metaData = ModelMetadata.FromLambdaExpression(expression, htmlHelper.ViewData);
if (!metaData.ModelType.IsEnum)
{
throw new ArgumentException("This helper is intended to be used with enum types");
}
var names = Enum.GetNames(metaData.ModelType);
var sb = new StringBuilder();
var fields = metaData.ModelType.GetFields(
BindingFlags.Static | BindingFlags.GetField | BindingFlags.Public
);
foreach (var name in names)
{
var id = string.Format(
"{0}_{1}_{2}",
htmlHelper.ViewData.TemplateInfo.HtmlFieldPrefix,
metaData.PropertyName,
name
);
var radio = htmlHelper.RadioButtonFor(expression, name, new { id = id }).ToHtmlString();
var field = fields.Single(f => f.Name == name);
var label = name;
var display = field
.GetCustomAttributes(typeof(DisplayAttribute), false)
.OfType<DisplayAttribute>()
.FirstOrDefault();
if (display != null)
{
label = display.GetName();
}
sb.AppendFormat(
"<label for=\"{0}\">{1}</label> {2}",
id,
HttpUtility.HtmlEncode(label),
radio
);
}
return MvcHtmlString.Create(sb.ToString());
}
}
Now if you have decorated some of the enum values with the DisplayAttribute, the values will come from the resource file.
You should replace in the extension method were it uses name for the <label> to use the resource you would like.
You should use a code kind of this one I adapted from here:
var type = typeof(metaData.ModelType);
var memInfo = type.GetMember(name);
var attributes = memInfo[0].GetCustomAttributes(typeof(DisplayAttribute), false);
var description = ((DisplayAttribute)attributes[0]).GetDescription();
And then put description into the <label>.
I've not tested it!

How to use dashes in HTML-5 data-* attributes in ASP.NET MVC

I am trying to use HTML5 data- attributes in my ASP.NET MVC 1 project. (I am a C# and ASP.NET MVC newbie.)
<%= Html.ActionLink("« Previous", "Search",
new { keyword = Model.Keyword, page = Model.currPage - 1},
new { #class = "prev", data-details = "Some Details" })%>
The "data-details" in the above htmlAttributes give the following error:
CS0746: Invalid anonymous type member declarator. Anonymous type members
must be declared with a member assignment, simple name or member access.
It works when I use data_details, but I guess it need to be starting with "data-" as per the spec.
My questions:
Is there any way to get this working and use HTML5 data attributes with Html.ActionLink or similar Html helpers ?
Is there any other alternative mechanism to attach custom data to an element? This data is to be processed later by JS.
This problem has been addressed in ASP.Net MVC 3. They now automatically convert underscores in html attribute properties to dashes. They got lucky on this one, as underscores are not legal in html attributes, so MVC can confidently imply that you'd like a dash when you use an underscore.
For example:
#Html.TextBoxFor(vm => vm.City, new { data_bind = "foo" })
will render this in MVC 3:
<input data-bind="foo" id="City" name="City" type="text" value="" />
If you're still using an older version of MVC, you can mimic what MVC 3 is doing by creating this static method that I borrowed from MVC3's source code:
public class Foo {
public static RouteValueDictionary AnonymousObjectToHtmlAttributes(object htmlAttributes) {
RouteValueDictionary result = new RouteValueDictionary();
if (htmlAttributes != null) {
foreach (System.ComponentModel.PropertyDescriptor property in System.ComponentModel.TypeDescriptor.GetProperties(htmlAttributes)) {
result.Add(property.Name.Replace('_', '-'), property.GetValue(htmlAttributes));
}
}
return result;
}
}
And then you can use it like this:
<%: Html.TextBoxFor(vm => vm.City, Foo.AnonymousObjectToHtmlAttributes(new { data_bind = "foo" })) %>
and this will render the correct data-* attribute:
<input data-bind="foo" id="City" name="City" type="text" value="" />
Update: MVC 3 and newer versions have built-in support for this. See JohnnyO's highly upvoted answer below for recommended solutions.
I do not think there are any immediate helpers for achieving this, but I do have two ideas for you to try:
// 1: pass dictionary instead of anonymous object
<%= Html.ActionLink( "back", "Search",
new { keyword = Model.Keyword, page = Model.currPage - 1},
new Dictionary<string,Object> { {"class","prev"}, {"data-details","yada"} } )%>
// 2: pass custom type decorated with descriptor attributes
public class CustomArgs
{
public CustomArgs( string className, string dataDetails ) { ... }
[DisplayName("class")]
public string Class { get; set; }
[DisplayName("data-details")]
public string DataDetails { get; set; }
}
<%= Html.ActionLink( "back", "Search",
new { keyword = Model.Keyword, page = Model.currPage - 1},
new CustomArgs( "prev", "yada" ) )%>
Just ideas, haven't tested it.
It's even easier than everything suggested above.
Data attributes in MVC which include dashes (-) are catered for with the use of underscore (_).
<%= Html.ActionLink("« Previous", "Search",
new { keyword = Model.Keyword, page = Model.currPage - 1},
new { #class = "prev", data_details = "Some Details" })%>
I see JohnnyO already mentioned this.
In mvc 4 Could be rendered with Underscore(" _ ")
Razor:
#Html.ActionLink("Vote", "#", new { id = item.FileId, }, new { #class = "votes", data_fid = item.FileId, data_jid = item.JudgeID, })
Rendered Html
<a class="votes" data-fid="18587" data-jid="9" href="/Home/%23/18587">Vote</a>
You can implement this with a new Html helper extension function which will then be used similarly to the existing ActionLinks.
public static MvcHtmlString ActionLinkHtml5Data(this HtmlHelper htmlHelper, string linkText, string actionName, string controllerName, object routeValues, object htmlAttributes, object htmlDataAttributes)
{
if (string.IsNullOrEmpty(linkText))
{
throw new ArgumentException(string.Empty, "linkText");
}
var html = new RouteValueDictionary(htmlAttributes);
var data = new RouteValueDictionary(htmlDataAttributes);
foreach (var attributes in data)
{
html.Add(string.Format("data-{0}", attributes.Key), attributes.Value);
}
return MvcHtmlString.Create(HtmlHelper.GenerateLink(htmlHelper.ViewContext.RequestContext, htmlHelper.RouteCollection, linkText, null, actionName, controllerName, new RouteValueDictionary(routeValues), html));
}
And you call it like so ...
<%: Html.ActionLinkHtml5Data("link display", "Action", "Controller", new { id = Model.Id }, new { #class="link" }, new { extra = "some extra info" }) %>
Simples :-)
edit
bit more of a write up here
I ended up using a normal hyperlink along with Url.Action, as in:
<a href='<%= Url.Action("Show", new { controller = "Browse", id = node.Id }) %>'
data-nodeId='<%= node.Id %>'>
<%: node.Name %>
</a>
It's uglier, but you've got a little more control over the a tag, which is sometimes useful in heavily AJAXified sites.
HTH
I do not like use pure "a" tag, too much typing. So I come with solution.
In view it look
<%: Html.ActionLink(node.Name, "Show", "Browse",
Dic.Route("id", node.Id), Dic.New("data-nodeId", node.Id)) %>
Implementation of Dic class
public static class Dic
{
public static Dictionary<string, object> New(params object[] attrs)
{
var res = new Dictionary<string, object>();
for (var i = 0; i < attrs.Length; i = i + 2)
res.Add(attrs[i].ToString(), attrs[i + 1]);
return res;
}
public static RouteValueDictionary Route(params object[] attrs)
{
return new RouteValueDictionary(Dic.New(attrs));
}
}
You can use it like this:
In Mvc:
#Html.TextBoxFor(x=>x.Id,new{#data_val_number="10"});
In Html:
<input type="text" name="Id" data_val_number="10"/>

ASP.NET MVC Checkbox Group

I am trying to formulate a work-around for the lack of a "checkbox group" in ASP.NET MVC. The typical way to implement this is to have check boxes of the same name, each with the value it represents.
<input type="checkbox" name="n" value=1 />
<input type="checkbox" name="n" value=2 />
<input type="checkbox" name="n" value=3 />
When submitted, it will comma delimit all values to the request item "n".. so Request["n"] == "1,2,3" if all three are checked when submitted. In ASP.NET MVC, you can have a parameter of n as an array to accept this post.
public ActionResult ActionName( int[] n ) { ... }
All of the above works fine. The problem I have is that when validation fails, the check boxes are not restored to their checked state. Any suggestions.
Problem Code: (I started with the default asp.net mvc project)
Controller
public class HomeController : Controller
{
public ActionResult Index()
{ var t = getTestModel("First");
return View(t);
}
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Index(TestModelView t)
{ if(String.IsNullOrEmpty( t.TextBoxValue))
ModelState.AddModelError("TextBoxValue", "TextBoxValue required.");
var newView = getTestModel("Next");
return View(newView);
}
private TestModelView getTestModel(string prefix)
{ var t = new TestModelView();
t.Checkboxes = new List<CheckboxInfo>()
{ new CheckboxInfo(){Text = prefix + "1", Value="1", IsChecked=false},
new CheckboxInfo(){Text = prefix + "2", Value="2", IsChecked=false}
};
return t;
}
}
public class TestModelView
{ public string TextBoxValue { get; set; }
public List<CheckboxInfo> Checkboxes { get; set; }
}
public class CheckboxInfo
{ public string Text { get; set; }
public string Value { get; set; }
public bool IsChecked { get; set; }
}
}
ASPX
<%
using( Html.BeginForm() ){
%> <p><%= Html.ValidationSummary() %></p>
<p><%= Html.TextBox("TextBoxValue")%></p>
<p><%
int i = 0;
foreach (var cb in Model.Checkboxes)
{ %>
<input type="checkbox" name="Checkboxes[<%=i%>]"
value="<%= Html.Encode(cb.Value) %>" <%=cb.IsChecked ? "checked=\"checked\"" : String.Empty %>
/><%= Html.Encode(cb.Text)%><br />
<% i++;
} %></p>
<p><input type="submit" value="submit" /></p>
<%
}
%>
Working Code
Controller
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Index(TestModelView t)
{
if(String.IsNullOrEmpty( t.TextBoxValue))
{ ModelState.AddModelError("TextBoxValue", "TextBoxValue required.");
return View(t);
}
var newView = getTestModel("Next");
return View(newView);
}
ASPX
int i = 0;
foreach (var cb in Model.Checkboxes)
{ %>
<input type="checkbox" name="Checkboxes[<%=i%>].IsChecked" <%=cb.IsChecked ? "checked=\"checked\"" : String.Empty %> value="true" />
<input type="hidden" name="Checkboxes[<%=i%>].IsChecked" value="false" />
<input type="hidden" name="Checkboxes[<%=i%>].Value" value="<%= cb.Value %>" />
<input type="hidden" name="Checkboxes[<%=i%>].Text" value="<%= cb.Text %>" />
<%= Html.Encode(cb.Text)%><br />
<% i++;
} %></p>
<p><input type="submit" value="submit" /></p>
Of course something similar could be done with Html Helpers, but this works.
I don't know how to solve your problem, but you could define your checkboxes with this code:
<%= Html.CheckBox("n[0]") %><%= Html.Hidden("n[0]",false) %>
<%= Html.CheckBox("n[1]") %><%= Html.Hidden("n[1]",false) %>
<%= Html.CheckBox("n[2]") %><%= Html.Hidden("n[2]",false) %>
Hidden fields are needed, because if checkbox is not checked, form doesn't send any value. With hidden field it sends false.
Your post method will be:
[HttpPost]
public ActionResult Test(bool[] n)
{
return View();
}
It may not be optimal and I am open to comments, but it works:)
EDIT
Extended version:
<%= Html.CheckBox("n[0].Checked") %><%= Html.Hidden("n[0].Value",32) %><%= Html.Hidden("n[0].Checked",false) %>
<%= Html.CheckBox("n[1].Checked") %><%= Html.Hidden("n[1].Value",55) %><%= Html.Hidden("n[1].Checked",false) %>
<%= Html.CheckBox("n[2].Checked") %><%= Html.Hidden("n[2].Value",76) %><%= Html.Hidden("n[2].Checked",false) %>
Your post method will be:
[HttpPost]
public ActionResult Test(CheckedValue[] n)
{
return View();
}
public class CheckedValue
{
public bool Checked { get; set; }
public bool Value { get; set; }
}
I wrote it without VS, so it may need little correction.
Behold the final solution:
public static class Helpers
{
public static string CheckboxGroup<TModel, TProperty>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TProperty>> propertySelector, int value) where TProperty: IEnumerable<int>
{
var groupName = GetPropertyName(propertySelector);
var modelValues = propertySelector.Compile().Invoke(htmlHelper.ViewData.Model);
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.Contains(value)))
{
builder.Attributes.Add("checked", null);
}
return builder.ToString(TagRenderMode.Normal);
}
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);
}
}
And in your view:
<%= Html.CheckboxGroup(model => model.DocumentCoverCustom, "1")%>(iv),<br />
<%= Html.CheckboxGroup(model => model.DocumentCoverCustom, "2")%>(vi),<br />
<%= Html.CheckboxGroup(model => model.DocumentCoverCustom, "3")%>(vii),<br />
<%= Html.CheckboxGroup(model => model.DocumentCoverCustom, "4")%>(ix)<br />
Well... the check boxes aren't going to know their state on their own, especially if you are not using the Html.CheckBox helper (if you are, see LuKLed's answer). You're going to have to put the checked state of each box in your ViewData (or Model) and then perform a look-up in your View in one way or another.
Warning: Really ugly proof-of-concept code:
Controller:
//validation fails
ViewData["checkboxn"] = n;
return View();
View:
<% int[] n = (int[])ViewData["checkboxn"]; %>
<input type="checkbox" name="n" value=1 <%= n != null && n.Contains(1) ? "checked=\"checked\"" : "" %> />
<input type="checkbox" name="n" value=2 <%= n != null && n.Contains(2) ? "checked=\"checked\"" : "" %> />
<input type="checkbox" name="n" value=3 <%= n != null && n.Contains(3) ? "checked=\"checked\"" : "" %> />
All I'm doing here is passing the array n back to the view, and if it contains a value for the respective checkbox, adding checked="checked" to the element.
You would probably want to refactor this into an HtmlHelper of your own, or otherwise make this less ugly, of course.
This solution may be of interest to those wanting a clean/simple approach:
Maintain state of a dynamic list of checkboxes in ASP.NET MVC
I wouldn't really recommend the use of Html.CheckBox unless you have a super simple, single checkbox bound to a single bool (or a couple of static ones at most). When you start having lists of checkboxes in a single array or dynamic checkboxes, it is difficult to work with and you end up programming the whole world in server side bloat just to deal with the shortfalls and get everything working. Forget it, and just use the clean HTML focused solution above and you're up and running quickly with less mess to maintain in the future.
I know this must be insanely late but just incase anyone else finds themselves here..
MVC does have a way to handle checkbox groups.
in your view model:
[Display(Name = "Which Credit Cards are Accepted:")]
public string[] EmployeeRoles{ get; set; }
On your Page:
<input id="EmployeeRoles" name="EmployeeRoles" type="checkbox" value="Supervisor"
#(Model.EmployeeRoles != null && Model.EmployeeRoles.Contains("Supervisor") ? "checked=true" : string.Empty)/>
<span>Supervisor</span><br />
<input id="EmployeeRoles" name="EmployeeRoles" type="checkbox" value="Auditor"
#(Model.EmployeeRoles != null && Model.EmployeeRoles.Contains("Auditor") ? "checked=true" : string.Empty)/>
<span>Auditor</span><br />
<input id="EmployeeRoles" name="EmployeeRoles" type="checkbox" value="Administrator"
#(Model.EmployeeRoles != null && Model.EmployeeRoles.Contains("Administrator") ? "checked=true" : string.Empty) />
<span>Administrator</span>
I tried very hard to create these controls with razor but no dice. It keeps
creating that hidden field you all have referred to. for your checkbox group
you don't need that hidden field, just the code I have added above. You can create an html helper to create this code for you.
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);
}
on your page use it like so:
#Html.CheckboxGroup(m => m.EmployeeRoles, typeof(Enums.EmployeeRoles))
I use an enum but you can use any kind of collection

Resources