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!
Related
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()
}
Based on my research, the correct way to code a checkbox in MVC is as follows.
#Html.CheckBoxFor(m => m.RememberMe)
#Html.LabelFor(m => m.RememberMe)
This will render a checkbox, render a label for the checkbox, and make the label clickable, which means clicking the label also toggles the checkbox.
But what about the case where there are multiple checkboxes for a single view-model field? (For example, maybe each checkbox represents a bit within an integer value.) Or, better yet, what about radio buttons, where there are always several options associated with the same field?
In these cases, there seems to be issues with the code above. Either not all elements would be associated with the same field, or multiple elements with the same ID would be rendered.
I have been researching this for a while now. I have found many solutions; however, they all seem to either not have a clickable label, or they require a bunch of custom and/or non-standard coding. Some avoid the use of RadioButtonFor/LabelFor completely, choosing instead to code the raw HTML.
Coding the raw HTML is fine, but didn't the designers of MVC not anticipate that we may need multiple radio buttons for the same field? And, if they did, how did they intended it to be coded?
EDIT:
After researching this some more, the code below is the best way I've found to code radio buttons. This code assumes I've defined an enum for each of my radio options.
#Html.RadioButtonFor(m => m.Gender, Gender.Male, new { id = "gender-male" })
#Html.LabelFor(m => m.Gender, Gender.Male.ToString(), new { #for = "gender-male" })
#Html.RadioButtonFor(m => m.Gender, Gender.Female, new { id = "gender-female" })
#Html.LabelFor(m => m.Gender, Gender.Female.ToString(), new { #for = "gender-female" })
Maybe this is the best I can expect, but it still troubles me a little.
Why is so much customization for basic stuff such as IDs required? Again, didn't Microsoft anticipate that we'd want multiple radio buttons associated with the same field?
if the enum name isn't the same as the description I want for the radio button label, this approach doesn't seem to support field attributes such as [Display(Name = "")].
(I guess the correct handling of multiple checkboxes will be deferred to another question.)
Ok, let's do some research. First of all, you don't need unique id to toggle checkbox/radio by clicking a label. Actually you don't need id at all! All you have to do is wrap your input inside <label> tag:
<label>
<input type="radio" name="radio" value="1" /> Radio 1
</label>
So far so good, ASP.NET MVC provides you ability to code your own html helpers, let's write one for Enum model field!
Imagine, we've got some registration model:
public class RegisterViewModel
{
[Required]
[EmailAddress]
[Display(Name = "Email")]
public string Email { get; set; }
[Required]
[StringLength(100, ErrorMessage = "The {0} must be at least {2} characters long.", MinimumLength = 6)]
[DataType(DataType.Password)]
[Display(Name = "Password")]
public string Password { get; set; }
[DataType(DataType.Password)]
[Display(Name = "Confirm password")]
[Compare("Password", ErrorMessage = "The password and confirmation password do not match.")]
public string ConfirmPassword { get; set; }
}
Damn! I've just copy-pasted tutorial model o_O. What we want now - is to get user's gender, so we add a Gender enum:
public enum Gender
{
Iamparanoic = 0,
Male = 1,
Female = 2,
}
and add Gender field of Gender type to our model (wish, c# had a Gender access modifier!)
public Gender Gender { get; set; }
Now, time to use some asp.net magic and write our html helper:
public static MvcHtmlString RadioButtonsListForEnum<TModel, TEnum>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TEnum>> expression)
{
var sb = new StringBuilder();
sb.Append("<ul>"); //we want it to look cool, dont' we?
foreach (var value in Enum.GetValues(typeof(TEnum)))
{
var radio = htmlHelper.RadioButtonFor(expression, value).ToHtmlString(); //you can generage any id and pass it to helper, or use a tagbuilder
sb.AppendFormat(
"<li><label>{0} {1}</label></li>",
radio,
((Enum)value).GetEnumName() //instead of it you can grab e.g. Display/Description attribute value or w/e else
);
}
sb.Append("</ul");
return MvcHtmlString.Create(sb.ToString());
}
and usage of that will look like:
#Html.RadioButtonsListForEnum(m => m.Gender)
So pretty, isn't it? Of course you can add hella lot's of customization, like rendering radiobuttons with unique mvc-style id's, all kindes of html attributes etc. but this is just a sample, ass kick to right way.
Next, we also want to render checkboxlist!
public static MvcHtmlString CheckBoxListForEnum<TModel, TEnum>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TEnum>> expression, IDictionary<string, object> htmlAttributes = null)
{
var metadata = ModelMetadata.FromLambdaExpression(expression, htmlHelper.ViewData);
var enumValue = Convert.ToInt32(metadata.Model);
var sb = new StringBuilder();
foreach (var value in Enum.GetValues(typeof (TEnum)))
{
var val = Convert.ToInt32(value);
var checkbox = new TagBuilder("input");
checkbox.MergeAttribute("type", "checkbox");
checkbox.MergeAttribute("value", value.ToString());
checkbox.MergeAttribute("name", htmlHelper.NameFor(expression).ToString());
if ((enumValue & val) == val)
checkbox.MergeAttribute("checked", "checked");
if (htmlAttributes != null)
checkbox.MergeAttributes(htmlAttributes);
var label = new TagBuilder("label") { InnerHtml = checkbox + ((Enum)value).GetEnumName() };
sb.Append(label);
sb.Append("<br/>");
}
return new MvcHtmlString(sb.ToString());
}
And again, simple usage:
#Html.CheckBoxListForEnum(m => m.Gender)
Btw, it makes sence just for enums marked with Flags attribute, and of course you'll have troubles with model binding - it requires custom binder. But it's another question, and i hope Jon Skeet can answer it :)
In my opinion, i believe nothing to be done from Microsoft side to handle the case you are talking about.
starting from the Html code, how in html, the multi radio buttons will be presented?
<form action="">
<input type="radio" name="sex" value="male">Male<br>
<input type="radio" name="sex" value="female">Female
</form>
above is a sample I brought it from W3cSchools,
if you want to toggle the radio button by clicking the label, this can be done in two ways in html
first way is to use the for tag in the label
<input type="radio" id="myId" name="MyName" value="MyValue" />
<label for="myId">Display Name</label>
second way is to put the radio input inside a label tag
<label>
<input type="radio" id="myId" name="MyName" value="MyValue"/>
Display Name
</label>
as you can see, its combination of tags to get the radio button toggles when you click on the label, Microsoft i think kept it simple and flexible to the developer to choose what to do and in the way he likes it.
now, to get the same result in Mvc, either you hard code each radio button you have in one of these 2 methods mentioned above, or to make it more easy, write your own Mvc Html extension, and I recommend the second method.
hope this will help you
I have finished my asp.net MVC web application, and I have been using the data annotation [Required] to mention that the field is required. But currently the required fields does not have any indication that they are required, unless the user tried to submit the form. So is there way to force my Razor view to display a red “” beside any field that have [Required] defined on it? OR I need to manually add the “” icon ?
Thanks
After I got burned by the Bootstrap 2 to 3 upgrade, where they pretty much completely changed the HTML for form controls, I've been putting the entire form group in editor templates instead of just the field. Here's an example:
<div class="form-group">
#Html.Label("", new { #class = string.Format("control-label col-md-2{0}", ViewData.ModelMetadata.IsRequired ? string.Empty : " optional") })
<div class="col-md-6">
#Html.TextBox("", ViewData.TemplateInfo.FormattedModelValue.ToString(), new { type = "email", #class = "form-control" })
#Html.ValidationMessage("")
</div>
</div>
What's important here for you is the string.Format in Html.Label. I'm using the ViewData.ModelMetadata.IsRequired to add an "optional" class if it's false. Bootstrap makes the labels bold by default, so as a required indicator, I make optional field labels normal (non-bold). However, adding a * is a little more difficult. You could use the same thing I'm doing here to add an additional span tag:
#if (ViewData.ModelMetadata.IsRequired)
{
<span class="requiredIndicator>*</span>
}
#Html.Label(...)
...
The potential problem is that that won't actually be inside your <label> tag, so you might have to do some extra styling work to make it look right depending on the styles you apply to the labels.
An alternative is to create your own HtmlHelper to return a label with a required indicator. Here's some sample code for that:
public static MvcHtmlString RequiredIndicatorLabelFor<TModel, TValue>(
this HtmlHelper<TModel> html,
Expression<Func<TModel, TValue>> modelProperty,
object htmlAttributes)
{
var metadata = ModelMetadata.FromLambdaExpression(modelProperty, html.ViewData);
var labelText = metadata.IsRequired ? string.Format("* {0}", metadata.GetDisplayName()) : metadata.GetDisplayName();
return html.LabelFor(modelProperty, labelText, htmlAttributes);
}
You can also write a custom label helper for this purpose
public static MvcHtmlString CustomLabelFor<TModel, TProperty>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TProperty>> expression,
IDictionary<string, object> htmlAttributes = null )
{
var metadata = ModelMetadata.FromLambdaExpression(expression, htmlHelper.ViewData);
var propertyName = ExpressionHelper.GetExpressionText(expression);
var builder = new TagBuilder("label");
builder.Attributes.Add("for", TagBuilder.CreateSanitizedId(htmlHelper.ViewContext.ViewData.TemplateInfo.GetFullHtmlFieldName(propertyName)));
var labelText = string.Format("{0}{1}", metadata.IsRequired ? "*" : string.Empty,
string.IsNullOrEmpty(metadata.DisplayName)
? metadata.PropertyName
: metadata.DisplayName);
builder.SetInnerText(labelText);
builder.MergeAttributes<string, object>(htmlAttributes, true);
return new MvcHtmlString(builder.ToString());
}
Now when used CustomLabelFor on a property with Required attribute, it will append * in fort of the label text.
#Html.CustomLabelFor(m => m.YourRequiredField)
I have build a CheckBoxListFor extension on HtmlHelper thanks to this wonderful answer http://bit.ly/Aevcea (code below) but am finding it doesn't post as expected.
My form is based on a model Group which has (amongst other properties) string Name and int[] PersonIDs.
The CheckBoxListFor renders something like this:
<ul>
<li><input type="checkbox" name="PersonIDs" value="1" id="PersonIDs_1" /></li>
<li><input type="checkbox" name="PersonIDs" value="2" id="PersonIDs_2" /></li>
</ul>
My controller has an Edit(Group group) method to handle submission of this form. However, upon submit I'm finding group.PersonIDs is null. There is though a Request.Form["PersonIDs"] set to the selected values (e.g. "1,2" if both items above are checked). Also, if I add another parameter to my Edit method (int[] PersonIDs) then that arrives with the expected contents (the selected IDs).
Can anyone explain what I'm doing wrong? The relevant bit of my view looks like this (extra bits stripped out):
#Html.TextBoxFor(m => m.Group.Name)
#Html.CheckBoxListFor(m => m.Group.PersonIDs, Model.MultiSelectListOfAllPeople)
Note that the group parameter in my Edit method does come back with Name set according to the form.
Just for completeness, here is the full body of my CheckBoxListFor extension:
public static MvcHtmlString CheckBoxListFor<TModel, TProperty>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, IEnumerable<TProperty>>> expression, MultiSelectList multiSelectList, object htmlAttributes = null)
{
//Derive property name for checkbox name
MemberExpression body = expression.Body as MemberExpression;
string propertyName = body.Member.Name;
//Get currently select values from the ViewData model
IEnumerable<TProperty> list = expression.Compile().Invoke(htmlHelper.ViewData.Model);
//Convert selected value list to a List<string> for easy manipulation
List<string> selectedValues = new List<string>();
if (list != null)
{
selectedValues = new List<TProperty>(list).ConvertAll<string>(delegate(TProperty i) { return i.ToString(); });
}
//Create div
TagBuilder wrapper = new TagBuilder("ul");
wrapper.AddCssClass("clearfix");
wrapper.MergeAttributes(new RouteValueDictionary(htmlAttributes), true);
//Add checkboxes
foreach (SelectListItem item in multiSelectList)
{
wrapper.InnerHtml += String.Format("<li><input type=\"checkbox\" name=\"{0}\" id=\"{0}_{1}\" " +
"value=\"{1}\" {2} /><label for=\"{0}_{1}\">{3}</label></li>",
propertyName,
item.Value,
selectedValues.Contains(item.Value) ? "checked=\"checked\"" : "",
item.Text);
}
return MvcHtmlString.Create(wrapper.ToString());
}
OK, the problem was that the CheckBoxListFor extension needed to render the control name as Group.PersonIDs and not simply PersonIDs. The form was bound to an object that itself was a sub-property of the view model. I've quickly adapted my CheckBoxListFor method as follows, but would gratefully accept a more elegant solution! I'm passing an additional boolean parameter includeDeclaringType to tell it whether to include the name of the declaring type in the ID. Not sure if this can be inferred any other way..?
public static MvcHtmlString CheckBoxListFor<TModel, TProperty>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, IEnumerable<TProperty>>> expression, MultiSelectList multiSelectList, bool includeDeclaringType, object htmlAttributes = null)
{
//Derive property name for checkbox name
MemberExpression body = expression.Body as MemberExpression;
string declaringTypeName = body.Member.DeclaringType.Name;
string propertyName = body.Member.Name;
//Get currently select values from the ViewData model
IEnumerable<TProperty> list = expression.Compile().Invoke(htmlHelper.ViewData.Model);
//Convert selected value list to a List<string> for easy manipulation
List<string> selectedValues = new List<string>();
if (list != null)
{
selectedValues = new List<TProperty>(list).ConvertAll<string>(delegate(TProperty i) { return i.ToString(); });
}
//Create div
TagBuilder wrapper = new TagBuilder("ul");
wrapper.AddCssClass("clearfix");
wrapper.MergeAttributes(new RouteValueDictionary(htmlAttributes), true);
//Add checkboxes
foreach (SelectListItem item in multiSelectList)
{
var name = string.Concat(
includeDeclaringType ? string.Format("{0}.", declaringTypeName) : "",
propertyName
);
var id = string.Concat(
includeDeclaringType ? string.Format("{0}_", declaringTypeName) : "",
propertyName,
"_",
item.Value
);
wrapper.InnerHtml += String.Format("<li><input type=\"checkbox\" name=\"{0}\" id=\"{1}\" " +
"value=\"{2}\" {3} /><label for=\"{1}\">{4}</label></li>",
name,
id,
item.Value,
selectedValues.Contains(item.Value) ? "checked=\"checked\"" : "",
item.Text);
}
return MvcHtmlString.Create(wrapper.ToString());
}
You can also use ExpressionHelper.GetExpressionText method, it will generate the correct input name for you:
var expressionText = ExpressionHelper.GetExpressionText(expression);
wrapper.InnerHtml +=
string.Format("<li><input type=\"checkbox\" name=\"{0}" id=\"{0}_{1}\" " +
"value=\"{1}\" {2} /><label for=\"{0}_{1}\">{3}</label></li>",
expressionText,
item.Value,
selectedValues.Contains(item.Value) ? "checked=\"checked\"" : "",
item.Text);
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/