Consider the following model and controller:
public class SimpleModel
{
[Required(ErrorMessage="Email Address is required.")]
[DataType(DataType.EmailAddress)]
[DisplayName("EmailAddress")]
public string EmailAddress { get; set; }
}
[HandleError]
public class SimpleController : Controller
{
public ActionResult Simple()
{
return View();
}
[HttpPost]
public ActionResult Simple(SimpleModel model)
{
if (ModelState.IsValid)
{
// handling code here
}
return View(model);
}
}
... and the relevant section in the matching view:
<% using (Html.BeginForm()) { %>
<%= Html.ValidationSummary(true, "The form submitted is not valid.") %>
<div>
<fieldset>
<div class="editor-label">
<%= Html.LabelFor(m => m.EmailAddress)%>
</div>
<div class="editor-field">
<%= Html.TextBoxFor(m => m.EmailAddress)%>
<%= Html.ValidationMessageFor(m => m.EmailAddress)%>
</div>
<div class="editor-field">
<input type="submit" value="Submit" />
</div>
</fieldset>
</div>
<% } %>
What would be the best way to modify the model, view and controller to support a dynamic number of email addresses defined by the controller.
Based on an article by Steve Sanderson I was able to find the elegant solution I was looking for:
First of all, the model needs to be modified as follows:
public class SimpleModel
{
public IEnumerable<EmailAddress> EmailAddresses { get; set; }
}
public class EmailAddress
{
[Required(ErrorMessage = "Email Address is required.")]
[DataType(DataType.EmailAddress)]
[DisplayName("Email Address")]
public string Value { get; set; }
}
The controller method handling the GET method needs to prepopulate the model with as many entries as required:
[HandleError]
public class SimpleController : Controller
{
public ActionResult Simple()
{
SimpleModel model = new SimpleModel
{
EmailAddresses =
new List<EmailAddress>
{
// as many as required
new EmailAddress { Value = string.Empty },
new EmailAddress { Value = string.Empty },
new EmailAddress { Value = string.Empty }
}
};
return View(model);
}
[HttpPost]
public ActionResult Simple(SimpleModel model)
{
if (ModelState.IsValid)
{
// handling code here
}
return View(model);
}
}
The view also needs to change:
<% using (Html.BeginForm()) { %>
<%= Html.ValidationSummary(true, "The form submitted is not valid.") %>
<div>
<fieldset>
<% foreach (var item in Model.EmailAddresses)
Html.RenderPartial("SimpleRows", item);
%>
<div class="editor-field">
<input type="submit" value="Submit" />
</div>
</fieldset>
</div>
<% } %>
... and a new partial view needs to be created. Note that the view is strongly-type with the type of the collection item.
<% using(Html.BeginCollectionItem("EmailAddresses")) { %>
<div class="editor-label">
<%= Html.LabelFor(x => x.Value)%>
</div>
<div class="editor-field">
<%= Html.TextBoxFor(x => x.Value)%>
<%= Html.ValidationMessageFor(x => x.Value)%>
</div>
<% }%>
The BeginCollectionItem is a Helper method created by Sanderson:
public static class HtmlPrefixScopeExtensions
{
private const string idsToReuseKey = "__htmlPrefixScopeExtensions_IdsToReuse_";
public static IDisposable BeginCollectionItem(this HtmlHelper html, string collectionName)
{
var idsToReuse = GetIdsToReuse(html.ViewContext.HttpContext, collectionName);
string itemIndex = idsToReuse.Count > 0 ? idsToReuse.Dequeue() : Guid.NewGuid().ToString();
// autocomplete="off" is needed to work around a very annoying Chrome behaviour whereby it reuses old values after the user clicks "Back", which causes the xyz.index and xyz[...] values to get out of sync.
html.ViewContext.Writer.WriteLine(string.Format("<input type=\"hidden\" name=\"{0}.index\" autocomplete=\"off\" value=\"{1}\" />", collectionName, html.Encode(itemIndex)));
return BeginHtmlFieldPrefixScope(html, string.Format("{0}[{1}]", collectionName, itemIndex));
}
public static IDisposable BeginHtmlFieldPrefixScope(this HtmlHelper html, string htmlFieldPrefix)
{
return new HtmlFieldPrefixScope(html.ViewData.TemplateInfo, htmlFieldPrefix);
}
private static Queue<string> GetIdsToReuse(HttpContextBase httpContext, string collectionName)
{
// We need to use the same sequence of IDs following a server-side validation failure,
// otherwise the framework won't render the validation error messages next to each item.
string key = idsToReuseKey + collectionName;
var queue = (Queue<string>)httpContext.Items[key];
if (queue == null) {
httpContext.Items[key] = queue = new Queue<string>();
var previouslyUsedIds = httpContext.Request[collectionName + ".index"];
if (!string.IsNullOrEmpty(previouslyUsedIds))
foreach (string previouslyUsedId in previouslyUsedIds.Split(','))
queue.Enqueue(previouslyUsedId);
}
return queue;
}
private class HtmlFieldPrefixScope : IDisposable
{
private readonly TemplateInfo templateInfo;
private readonly string previousHtmlFieldPrefix;
public HtmlFieldPrefixScope(TemplateInfo templateInfo, string htmlFieldPrefix)
{
this.templateInfo = templateInfo;
previousHtmlFieldPrefix = templateInfo.HtmlFieldPrefix;
templateInfo.HtmlFieldPrefix = htmlFieldPrefix;
}
public void Dispose()
{
templateInfo.HtmlFieldPrefix = previousHtmlFieldPrefix;
}
}
}
... and that's it... when you post the form the model will be automatically populated and passed to the controller action handling the POST method.
Note that with this solution all the attributes including the validation work as expected.
public class SimpleModel
{
[Required(ErrorMessage = "Email Address is required.")]
[DataType(DataType.EmailAddress)]
[DisplayName("EmailAddress")]
public List<string> EmailAddress { get; set; }
}
[HandleError]
public class SimpleController : Controller
{
public ActionResult SimpleTest()
{
SimpleModel model = new SimpleModel();
model.EmailAddress = new List<string>();
model.EmailAddress.Add("email1");
model.EmailAddress.Add("email2");
return View(model);
}
[HttpPost]
public ActionResult SimpleTest(FormCollection formvalues)
{
if (ModelState.IsValid)
{
// handling code here
}
SimpleModel model = new SimpleModel();
model.EmailAddress = new List<string>();
model.EmailAddress.Add("email1");
model.EmailAddress.Add("email2");
return View();
}
}
... and the relevant section in the matching view:
<% using (Html.BeginForm())
{%>
<%: Html.ValidationSummary(true)%>
<fieldset>
<legend>Fields</legend>
<%{
foreach (var i in Model.EmailAddress)
{ %>
<div class="editor-label">
<%: Html.LabelFor(model => model.EmailAddress)%>
</div>
<div class="editor-field">
<%: Html.TextBoxFor(model =>i)%>
<br />
</div>
<%}
}%>
<p>
<input type="submit" value="Create" />
</p>
</fieldset>
<% } %>
I don't think the validation will work though.
I also marked the answer by #JCallico as correct.
For any die-hard VB coders here's the extension method by Steve Sanderson in your favourite language:
'from http://stackoverflow.com/questions/5236251/form-with-dynamic-number-of-texboxes-in-asp-net-mvc based on http://blog.stevensanderson.com/2010/01/28/editing-a-variable-length-list-aspnet-mvc-2-style/
Imports System.IO
Imports System.Web.Mvc
Imports System.Web
Imports System.Collections.Generic
Namespace HtmlHelpers.BeginCollectionItem
Public Module HtmlPrefixScopeExtensions
Private Const IdsToReuseKey As String = "__htmlPrefixScopeExtensions_IdsToReuse_"
<System.Runtime.CompilerServices.Extension> _
Public Function BeginCollectionItem(html As HtmlHelper, collectionName As String) As IDisposable
Return BeginCollectionItem(html, collectionName, html.ViewContext.Writer)
End Function
<System.Runtime.CompilerServices.Extension> _
Public Function BeginCollectionItem(html As HtmlHelper, collectionName As String, writer As TextWriter) As IDisposable
Dim idsToReuse = GetIdsToReuse(html.ViewContext.HttpContext, collectionName)
Dim itemIndex = If(idsToReuse.Count > 0, idsToReuse.Dequeue(), Guid.NewGuid().ToString())
' autocomplete="off" is needed to work around a very annoying Chrome behaviour
' whereby it reuses old values after the user clicks "Back", which causes the
' xyz.index and xyz[...] values to get out of sync.
writer.WriteLine("<input type=""hidden"" name=""{0}.index"" autocomplete=""off"" value=""{1}"" />", collectionName, html.Encode(itemIndex))
Return BeginHtmlFieldPrefixScope(html, String.Format("{0}[{1}]", collectionName, itemIndex))
End Function
<System.Runtime.CompilerServices.Extension> _
Public Function BeginHtmlFieldPrefixScope(html As HtmlHelper, htmlFieldPrefix As String) As IDisposable
Return New HtmlFieldPrefixScope(html.ViewData.TemplateInfo, htmlFieldPrefix)
End Function
Private Function GetIdsToReuse(httpContext As HttpContextBase, collectionName As String) As Queue(Of String)
' We need to use the same sequence of IDs following a server-side validation failure,
' otherwise the framework won't render the validation error messages next to each item.
Dim key = IdsToReuseKey & collectionName
Dim queue = DirectCast(httpContext.Items(key), Queue(Of String))
If queue Is Nothing Then
httpContext.Items(key) = InlineAssignHelper(queue, New Queue(Of String)())
Dim previouslyUsedIds = httpContext.Request(collectionName & Convert.ToString(".index"))
If Not String.IsNullOrEmpty(previouslyUsedIds) Then
For Each previouslyUsedId In previouslyUsedIds.Split(","c)
queue.Enqueue(previouslyUsedId)
Next
End If
End If
Return queue
End Function
Friend Class HtmlFieldPrefixScope
Implements IDisposable
Friend ReadOnly TemplateInfo As TemplateInfo
Friend ReadOnly PreviousHtmlFieldPrefix As String
Public Sub New(templateInfo__1 As TemplateInfo, htmlFieldPrefix As String)
TemplateInfo = templateInfo__1
PreviousHtmlFieldPrefix = TemplateInfo.HtmlFieldPrefix
TemplateInfo.HtmlFieldPrefix = htmlFieldPrefix
End Sub
Public Sub Dispose() Implements System.IDisposable.Dispose
TemplateInfo.HtmlFieldPrefix = PreviousHtmlFieldPrefix
End Sub
End Class
Private Function InlineAssignHelper(Of T)(ByRef target As T, value As T) As T
target = value
Return value
End Function
End Module
End Namespace
I am happily using this with Value Injector.
Related
I'd like to know your opinion about this way used to submit complex models in MVC
For example, if I have a complex model like this:
public class TOIFormDTO
{
public TradeFormDTO Trade { get; set; }
public OrderFormDTO Order { get; set; }
public ItemFormDTO Item { get; set; }
public TOIFormDTO() {
Trade = new TradeFormDTO();
Order = new OrderFormDTO();
Item = new ItemFormDTO();
}
}
And, I bind it to the view in the form:
#using (Html.BeginForm("SaveTOI", "Item", FormMethod.Post, new {id = "FormTOI" }))
{
<div id="tabs" style="width:1100px; height:500px">
<ul>
<li>Trade</li>
<li>Order</li>
<li>Item</li>
</ul>
<fieldset>
<legend></legend>
<div id="tabTrade">
#Html.PartialForCustomPrefix(model => model.Trade, "~/Views/Trade/Trade.cshtml", "Trade")
</div>
<div id="tabOrder" style="display: none">
#Html.PartialForCustomPrefix(model => model.Order, "~/Views/Order/Order.cshtml", "Order")
</div>
<div id="tabItem">
#Html.PartialForCustomPrefix(model => model.Item, "~/Views/Item/Item.cshtml", "Item")
</div>
</fieldset>
</div>
}
By putting a prefix this way:
public static MvcHtmlString PartialForCustomPrefix<TModel, TProperty>(this HtmlHelper<TModel> helper, System.Linq.Expressions.Expression<Func<TModel, TProperty>> expression, string partialViewName, string CustomPrefix)
{
object model = ModelMetadata.FromLambdaExpression(expression, helper.ViewData).Model;
var viewData = new ViewDataDictionary(helper.ViewData)
{
TemplateInfo = new System.Web.Mvc.TemplateInfo
{
HtmlFieldPrefix = CustomPrefix
}
};
return helper.Partial(partialViewName, model, viewData);
}
This way, when the form is submitted the model is correctly 'recognized' by the action:
public ActionResult SaveTOI(TOIFormDTO toiToSave)
{
}
Do you think this is a good way to procede, is it solid?
Or do you recognized some potential failure?
Hello as I'm so new to Mvc I woild appreciate any Help!
So Here is myModels
Category
-CategoryID
-DateCreated
CategoriesLanguages
ID (autoincrement)
CategoryID
LanguageID
Title
Description
Basically I want to be able when I click AddNew Button - I create a new record in Category table - and I'm having the id of the created category.
And in the Create() view which Create() action is returning I gave the user opportunity to fill the description and title of categorylanguages.
ANd when the user clicks the submit button he should be redirected to Create(CategoryLanguages) acton which as you see accepts an object of CategoryLanguage and this action will simply store this object in the database. My QUESTION IS HOW TO RETURN this object!
public class CategoryController : Controller
{
public ActionResult Create()
{
CategoryViewModel vm = new CategoryViewModel();
vm.AddNewCategory();
return View(vm);
}
pubcli AcrionResult Create(CategoryLanguage ob)
{
CategoryViewModel vm = new CategoryViewModel();
vm.SaveInDatabaseCategorylanguage(ob);
return RedirectToAction("Index");
}
}
And Here is my View CreateView.csHtml
#model MvcApplication1.ViewModel.CategoryViewModel
/
#using (Html.BeginForm())
{
<fieldset class="form-horizontal">
<legend>Category</legend>
// Here i should have a dropdpwn and teh selected value I should get it for LanguageID
<div class="control-group">
#Html.LabelFor(model => model.modelcatlang.Title, new { #class = "control-label" })
<div class="controls">
#Html.EditorFor(model => model.modelcatlang.Title) //From here i should get title
</div>
</div>
<div class="control-group">
#Html.LabelFor(model => model.modelcatlang.Description, new { #class = "control-label" })
<div class="controls">
#Html.EditorFor(model => model.modelcatlang.Description) //from here I should get the description
</div>
</div>
<div class="form-actions no-color">
<input type="submit" value="Create" class="btn" />
/*somehow ewhen i click this button I should make
CategoryLanguage catlang= new CategoryLanguahe;
catLang.CatID = insertedID (I have it nio problem);
catlang.lanID = dropdown.value;
catlang.Title = from title from editorform
...*/
</div>
</fieldset>
}
<div>
#Html.ActionLink("Back to List", "Index")
</div>
EDIT
I'm posting the code for My CategoryViewModel
public class CategoryViewModel
{
public CategoryLanguages modelcatlang { get; set; }
public int newCategoryID { get; set; }
public List<Language> lstLanguages { get; set; }
public List<CategoryLanguages> lstCategoryLanguages { get; set; }
public CategoryLanguages categoryToEdit { get; set; }
private readonly ICategoryRepository catRep;
private readonly ILanguageRepository lanRep;
private readonly ICategoryLanguageRepository catlanRep;
public CategoryViewModel()
: this(new CategoryRepository(), new LanguageRepository(), new CategoryLanguageRepository())
{
}
public CategoryViewModel(ICategoryRepository catRep, ILanguageRepository lanRep, ICategoryLanguageRepository catlanRep)
{
this.catRep = catRep;
this.lanRep = lanRep;
this.catlanRep = catlanRep;
}
public void AddNewCategory()
{
lstLanguages = lanRep.GetAllAvailableLanguages();
newCategoryID = catRep.AddCategory();
modelcatlang = new CategoryLanguages();
}
So in the ViewModel in AddNewCategory() Method i have the id of the just inserted Category in category table (in newCategoryID)
I also make an instance of CategoryLanguage().SO maybe my new question will be - how can i fill all the properties of modelcatlang object(part of my viewmodel) and return it to Create(CategoriesLanguages) action
I am quite new to MVC, and am having a bit of trouble submitting a form and having the controller pick up the posted values.
What seems to be happening is that while the form does post to the correct method in the controller, the model that is passed through is full of empty values - as if it's not being populated by the form.
I've tried to create it in the same way as the default Login control, but I'm obviously missing something somewhere. Can anyone please shed any light?
My code is below:
MODEL
Public Class ContactUsDetails
Private _name As String
Private _email As String
Private _details As String
Public ReadOnly Property Name() As String
Get
Return _name
End Get
End Property
Public ReadOnly Property Email() As String
Get
Return _email
End Get
End Property
Public ReadOnly Property Details() As String
Get
Return _details
End Get
End Property
Public Sub New(ByVal name As String, ByVal email As String, ByVal details As String)
_name = name
_email = email
_details = details
End Sub
Public Sub New
End Sub
End Class
VIEW
#ModelType TestMVC.ContactUsDetails
#Code
ViewData("Title") = "ContactUs"
End Code
#Using Html.BeginForm()
#<fieldset>
<legend>Contact Us</legend>
<div class="editor-label">
#Html.LabelFor(Function(m) m.Name)
</div>
<div class="editor-field">
#Html.TextBoxFor(Function(m) m.Name)
</div>
<div class="editor-label">
#Html.LabelFor(Function(m) m.Email)
</div>
<div class="editor-field">
#Html.TextBoxFor(Function(m) m.Email)
</div>
<div class="editor-label">
#Html.LabelFor(Function(m) m.Details)
</div>
<div class="editor-field">
#Html.TextBoxFor(Function(m) m.Details)
</div>
<p>
<input type="submit" value="Submit" />
</p>
</fieldset>
End Using
CONTROLLER
Namespace TestMVC
Public Class FormsController
Inherits System.Web.Mvc.Controller
'
' GET: /Forms
Public Function ContactUs() As ActionResult
Return View()
End Function
<HttpPost()> _
Public Function ContactUs(model As ContactUsDetails) As ActionResult
If ModelState.IsValid Then
End If
Return View(model)
End Function
End Class
End Namespace
I'm not too expert with VB, but I your model should have the properties editable, looking at your code it seems that your modle is readonly. So the model binder can not fill in the values
The model binder does not populate the model by calling a constructor, but by setting property values. Hence, your model properties must not my read only.
MODEL:
public class FileSetViewModel
{
public int FileId { get; set; }
[DisplayName("From Policy")]
public string FromPolicy { get; set; }
[DisplayName("Policy location")]
public string PolicyLocation { get; set; }
[DisplayName("Policy type")]
public string PolicyType { get; set; }
[DisplayName("File name")]
public string FileName { get; set; }
[DisplayName("Device Type")]
public string DeviceType { get; set; }
}
public class FileSetListViewModel
{
public List<FileSetViewModel> FileSetList { get; set; }
}
VIEW:
#section DeviceContent {
<h2>File set</h2>
#if (Model.FileSetList.Count() > 0)
{
<table>
<caption>Files loaded on current device</caption>
<thead>
<tr>
<th scope="col">From Policy</th>
<th scope="col">Policy Location</th>
<th scope="col">Policy Type</th>
<th scope="col">File Name</th>
<th scope="col">Device Type</th>
<th scope="col">View</th>
</tr>
</thead>
<tbody>
#foreach (var fileSet in Model.FileSetList)
{
<tr>
<td>#fileSet.FromPolicy</td>
<td>#fileSet.PolicyLocation</td>
<td>#fileSet.PolicyType</td>
<td>#fileSet.FileName</td>
<td>#fileSet.DeviceType</td>
<td>View</td>
</tr>
}
</tbody>
</table>
}
}
Controller:
[HttpGet]
public ActionResult Index(int id)
{
FileSetListViewModel model = _policiesLogic.GetFilesSetForDevice(id);
return View(model);
}
[HttpPost]
public ActionResult Index(FileSetListViewModel model)
{
// preconditions
if (null == model) throw new ArgumentNullException("model");
if (ModelState.IsValid)
{
// Do stuff
}
else // Validation error, so redisplay data entry form
{
return View(model);
}
}
Even if the model is empty i always pass an instance to the view, though i could be wrong as this is my first mvc project as well...
I don't know if I am explaining this correctly, or if the solution is rather simple, so here goes:
I am using MvcMailer, but before that I set up a wizard input form which I call Quote.cshtml. Behind Quote.cshtml, I set up a model called QuoteModel.cs.
Quote.cshtml at its most basic (I am leaving out all of the wizard logic and only showing one input):
<td width="40%">
#Html.LabelFor(m => m.FirstName, new { #class = "mylabelstyle", title = "Enter first name." })
</td>
<td width="60%">
#Html.TextBoxFor(m => m.FirstName)
#Html.ValidationMessageFor(m => m.FirstName)
</td>
QuoteModel.cs (again, only showing the one input; n.b.: using the DataAnnotationExtensions)
public class QuoteModel
{
[Required(ErrorMessage = "First Name required.")]
[Display(Name = "First Name:")]
public string FirstName { get; set; }
}
Now I am trying to integrate MvcMailer, which sets up IQuoteMailer.cs, QuoteMailer.cs, _Layout.cshtml, and QuoteMail.cshtml. The QuoteMail.cshtml is what the recipient of the mail will eventually see. I also set up a QuoteController.cs, in which I placed the appropriate code required by MvcMailer. It is in the QuoteMailer.cs and QuoteController.cs where I am having trouble passing the user input from Quote.cshtml (which is based on the model in QuoteModel.cs).
IQuoteMailer.cs:
public interface IQuoteMailer
{
MailMessage QuoteMail();
}
QuoteMailer.cs:
public class QuoteMailer : MailerBase, IQuoteMailer
{
public QuoteMailer():
base()
{
MasterName="_Layout";
}
public virtual MailMessage QuoteMail()
{
var mailMessage = new MailMessage{Subject = "QuoteMail"};
mailMessage.To.Add("some-email#example.com");
ViewBag.Data = someObject;
//I imagine this is where I can pass my model,
//but I am not sure (do I have to iterate each and
//every input (there are like 20 in QuoteModel.cs)?
return mailMessage;
}
QuoteMail.cshtml (_Layout.cshtml is pretty standard, so not showing here):
#*HTML View for QuoteMailer#QuoteMail*#
Welcome to MvcMailer and enjoy your time!<br />
<div class="mailer_entry">
<div class="mailer_entry_box">
<div class="mailer_entry_text">
<h2>
INSERT_TITLE
</h2>
<p>
INSERT_CONTENT
//I believe I am going to use a "#" command like #ViewData
//to pass FirstName, but again, not sure how to bind
//the model and then pass it.
</p>
<p>
INSERT_CONTENT
</p>
</div>
</div>
</div>
And finally, the relevant parts of the QuoteController.cs (note that I have am using a wizard, therefore, part of my problem is figuring out where to put the MvcMailer code, but I think I may have it right):
public class QuoteController: Controller
{
/// <summary>
/// MvcMailer
/// </summary>
private IQuoteMailer _quoteMailer = new QuoteMailer();
public IQuoteMailer QuoteMailer
{
get { return _quoteMailer; }
set { _quoteMailer = value; }
}
//
// GET: /Quote/
[HttpGet]
public ActionResult Quote()
{
HtmlHelper.ClientValidationEnabled = true;
HtmlHelper.UnobtrusiveJavaScriptEnabled = true;
//In order to get the clientside validation (unobtrusive),
//the above lines are necessary (where action takes place)
return View();
}
//
// POST: /Matrimonial/
[HttpPost]
public ActionResult Quote(QuoteModel FinishedQuote)
{
if (ModelState.IsValid)
{
QuoteMailer.QuoteMail().Send();
return View("QuoteMailSuccess", FinishedQuote);
}
else return View();
}
//
// POST: /Matrimonial/Confirm
[HttpPost]
public ActionResult QuoteMailConfirm(QuoteModel FinishedQuote)
{
return PartialView(FinishedQuote);
}
}
So, my confusion is to how to pass the QuoteModel I created, so that ultimately I can take the user inputed data and then generate the MvcMailer view.
I appreciate the communities help.
You could have the IQuoteMailer interface take the model:
public interface IQuoteMailer
{
MailMessage QuoteMail(QuoteModel model);
}
and in the implementation use this model:
public class QuoteMailer : MailerBase, IQuoteMailer
{
public QuoteMailer() : base()
{
MasterName = "_Layout";
}
public virtual MailMessage QuoteMail(QuoteModel model)
{
var mailMessage = new MailMessage
{
Subject = "QuoteMail"
};
mailMessage.To.Add("some-email#example.com");
// Use a strongly typed model
ViewData = new ViewDataDictionary(model);
PopulateBody(mailMessage, "QuoteMail", null);
return mailMessage;
}
}
then from the controller when you decide to send the mail pass the model:
[HttpPost]
public ActionResult Quote(QuoteModel FinishedQuote)
{
if (ModelState.IsValid)
{
QuoteMailer.QuoteMail(FinishedQuote).Send();
return View("QuoteMailSuccess", FinishedQuote);
}
else return View();
}
and finally in the template (~/Views/QuoteMailer/QuoteMail.cshtml) you could use the model:
#using AppName.Models
#model QuoteModel
Welcome to MvcMailer and enjoy your time!
<br />
<div class="mailer_entry">
<div class="mailer_entry_box">
<div class="mailer_entry_text">
<h2>
INSERT_TITLE
</h2>
<p>
Hello #Model.FirstName
</p>
<p>
INSERT_CONTENT
</p>
</div>
</div>
</div>
I am trying to validate a form in MVC.
I add custom errors to model state and get it as invalid when form is submitted. When the view is displayed it doesn’t show validation messages nor validation summary. Can anyone please let me know what am I doing wrong or point me in the right direction if there is any other way of validating?
Edit This is ASP.NET MVC 1. Here's the code:
Following is the entity
namespace DCS.DAL.Entities
{
public class Group : IDataErrorInfo
{
public int GroupId { get; set; }
public string GroupName { get ; set; }
public string AboutText { get; set; }
public string LogoURL { get; set; }
public string FriendlyURL { get; set; }
public bool ExcludeFromFT { get; set; }
public ContactInfo ContactInfo { get; set; }
public string Error { get { return string.Empty; } }
public string this[string propName]
{
get
{
if ((propName == "GroupName") && string.IsNullOrEmpty(GroupName))
return "Please enter Group Name";
return null;
}
}
}
}
Following is the view
<%= Html.ValidationSummary("Please correct following details") %>
<% using (Html.BeginForm()) {%>
<div id="divError" Style="display:none;">
errors
<%
foreach (KeyValuePair<string, ModelState> keyValuePair in ViewData.ModelState)
{
foreach (ModelError modelError in keyValuePair.Value.Errors)
{
%>
<% Response.Write(modelError.ErrorMessage); %>
<%
}
}
%>
</div>
<fieldset>
<table>
<tr>
<td>
<label for="GroupName">Group Name:</label>
</td>
<td>
<%= Html.TextBox("GroupName", Model.GroupName) %>
<%= Html.ValidationMessage("GroupName","group") %>
</td>
Foreach loop is for testing, it does gets into the for loop but doesn’t response.write error message nor validation summary nor validation message.
Following is the controller
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult EditGroup(Group group, FormCollection collection)
{
//Group group = new Group();
bool success = false;
try
{
var contactInfo = new ContactInfo
{
ContactName = collection["ContactName"],
Email = collection["Email"],
Fax = collection["Fax"],
HeadOfficeAddress = collection["HeadOfficeAddress"],
Freephone = collection["Freephone"],
Telephone = collection["Telephone"],
Website = collection["Website"]
};
group.ContactInfo = contactInfo;
group.GroupName = collection["GroupName"];
if(string.IsNullOrEmpty(group.GroupName))
{
ModelState.AddModelError("GroupName", "Please enter group name");
}
if (!ModelState.IsValid)
{
success = groupRepository.InsertUpdateGroup(group);
return View(group);
}
}
catch
{
}
//return Json(success);
return View(group);
}
It does go into the if(!Modelstate.isvalid) loop but it doesn’t display error.
Edit 2 I can see in the Text Visualiser that the Validation Summary does have the error message, it wont display on screen though.
Thanks
You could decorate your model properties with data annotation attributes allowing you to perform some validation logic. Here's a simplified example:
Model:
public class Group
{
[Required]
public string GroupName { get; set; }
}
Controller:
public class HomeController : Controller
{
public ActionResult Index()
{
return View(new Group());
}
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Index(Group group)
{
// Remark: You don't need the FormCollection as argument to this action,
// leave the default model binder do the job - it will also work
// for the ContactInfo property as long as you name your text fields
// appropriately. For example Html.TextBox("ContactInfo.Email", Model.ContactInfo.Email)
return View(group);
}
}
View:
<% using (Html.BeginForm()) { %>
<label for="GroupName">Group Name:</label>
<%= Html.TextBox("GroupName", Model.GroupName) %>
<%= Html.ValidationMessage("GroupName", "group") %>
<input type="submit" value="Post" />
<% } %>
It's up to you to decide whether Data Annotations are sufficient for your case, but bear in mind that if you need to perform more advanced validation scenarios you might take a look at third party frameworks like FluentValidation and xVal.