Duplicate Rules appearing on MVC Home Page - c#

I have been following a article on Visual Studio Magazine and have found that for some reason I am getting duplicates for Rules in the drop down on my page. Could someone please explain what I am missing here? Also I would like to know how I could fix this issue.
Upon closer inspection I believe the issue lies with the Validators line as it appears to be reading in 4 objects rather than 2 (a Phone validator and an Email validator). The Email validator code is essentially the same as the Phone but using a different regex.
Controller code:
[ImportMany]
public IEnumerable<Lazy<BusinessRules.IValidate<string>, BusinessRules.IValidateMetaData>> Validators { get; private set; }
[HttpGet]
public ActionResult Index()
{
var vm = new ValidationFormModel();
vm.Rules = new List<SelectListItem>(from v in Validators
select new SelectListItem()
{ Text = v.Metadata.Name,
Value = v.Metadata.Name });
return View(vm);
}
View Model code:
public class ValidationFormModel
{
public string Input { get; set; }
public List<SelectListItem> Rules { get; set; }
public string Rule { get; set; }
public string StatusLabel { get; set; }
}
Business Rules Code
[Export(typeof(IValidate<string>))]
[ExportMetadata("Name", "Phone")]
public class ValidatePhone : IValidate<string>
{
const string PHONE_PATTERN = #"^((\(\d{3}\) ?)|(\d{3}-))?\d{3}-\d{4}$";
public ValidationResult Validate(string input)
{
var result = new ValidationResult();
if (input == null || !Regex.IsMatch(input, PHONE_PATTERN))
{
result.ErrorMessage = string.Format("{0} is not a valid phone number");
}
else
{
result.IsValid = true;
}
return result;
}
}
The View:
#model ValidationExample.ViewModels.ValidationFormModel
#{
ViewBag.Title = "MEF Demo";
}
#using (Html.BeginForm())
{
<strong>#Html.DisplayFor(m => m.StatusLabel)</strong>
#Html.ValidationSummary(false)
<fieldset>
<legend>Validation Demo</legend>
#Html.LabelFor(m => m.Input)
#Html.TextBoxFor(m => m.Input)
#Html.LabelFor(m => m.Rule)
#Html.DropDownListFor(m => m.Rule, Model.Rules)
</fieldset>
<input type="submit" />
}

From the view, try using IEnumerable
#model IEnumerable<ValidationExample.ViewModels.ValidationFormModel>
and try changing your dropdown list to a normal <select>
<fieldset>
<legend>Validation Demo</legend>
#Html.LabelFor(m => m.Input)
#Html.TextBoxFor(m => m.Input)
#Html.LabelFor(m => m.Rule)
foreach(var item in Model)
{
<select>#item.Rule </select>
}
</fieldset>

Related

MVC ViewModel not posting back

I have seen lots of examples on this, but cannot get any working. I have built this example to prove/disprove passing back of the view model SomeDataViewModel.
I am trying to post back the dropdownlist data. Everything works ok, but the OtherData property on TestViewModel never returns the collect that was passed in.
Have tried adding:
#Html.HiddenFor(m => Model.OtherData)
but again this just produces the following error;
The parameter conversion from type 'System.String' to type 'SomeDataViewModel' failed because no type converter can convert between these types
The Code:
ViewModels
TestViewmodel
public class TestViewModel
{
public TestViewModel()
{
OtherData = new List<SomeDataViewModel>();
}
public int Id { get; set; }
public String Name { get; set; }
public DateTime DoB { get; set; }
public int SelectedOtherData { get; set; }
public List<SomeDataViewModel> OtherData { get; set; }
public IEnumerable<SelectListItem> TolistData()
{
IEnumerable<SelectListItem> ret = OtherData.Select(i => new SelectListItem() { Text = i.Text, Value=i.Value });
return ret;
}
}
SomeDataViewmodel
public class SomeDataViewModel
{
public string Value { get; set; }
public string Text { get; set; }
}
View
#model TestViewModel
#{
ViewBag.Title = "Home Page";
}
#using (Html.BeginForm("Index","Home"))
{
<div class="row">
<div class="col-md-12">
<br />
#Html.EditorFor(m => Model.Id)
<br />
#Html.EditorFor(m => Model.Name)
<br />
#Html.EditorFor(m => Model.DoB)
<br/>
#Html.DropDownListFor(m => Model.SelectedOtherData, Model.TolistData(), new { id = "OtherData" })
<p><a class="btn btn-default" href="http://go.microsoft.com/fwlink/?LinkId=301865">Learn more ยป</a></p>
</div>
</div>
<button id="dosomething" formmethod="post">Post</button>
}
Controller
public ActionResult Index()
{
var model = new TestViewModel() {
Id = 99,
Name = "Billy",
DoB = DateTime.Now
};
model.OtherData.Add(
new SomeDataViewModel { Text = "Bob", Value = "1" });
model.OtherData.Add(
new SomeDataViewModel { Text = "Sally", Value = "2" });
return View(model);
}
[HttpPost]
public ActionResult Index(TestViewModel retModel)
{
if (ModelState.IsValid)
{
if (retModel.OtherData.Count() == 0)
{
var dud = true;
}
}
return View(retModel);
}
You can't render hidden inputs for complex data with #Html.HiddenFor helper.
You should use it only with simple types. Becouse you got array you should write something like this:
#for(int i = 0; i < Model.OtherData.Count(); i++)
{
#Html.HiddenFor(m => Model.OtherData[i].Text)
#Html.HiddenFor(m => Model.OtherData[i].Value)
//... other fields.
#Html.HiddenFor(m => Model.OtherData[i].OtherProperty)
}
Use for loop instead of foreach becouse you should same your mappings for right binding on form POST.
Certainly there is a type conversion error. your SelectedOtherData is type of int while selectlistitem value is type of string

MVC4 DropDownList from DB

I'm trying to make very simple forum, but I have problem with DropDownList. I have two models:
ForumThread.cs
public partial class ForumThread
{
public ForumThread()
{
this.ForumCategory = new HashSet<ForumCategory>();
}
public int TH_ID { get; set; }
public System.DateTime DATE { get; set; }
public string TOPIC { get; set; }
public string USER { get; set; }
public virtual ICollection<ForumCategory> ForumCategory { get; set; }
}
ForumCategory.cs
public partial class ForumCategory
{
public ForumCategory()
{
this.ForumThread = new HashSet<ForumThread>();
}
public int CA_ID { get; set; }
public string CATEGORY { get; set; }
public bool isSelected { get; set; }
public virtual ICollection<ForumThread> ForumThread { get; set; }
}
I tried to make "Create" function with view:
Create
#model AnimeWeb.Models.ForumThread
#{
ViewBag.Title = "Create";
}
<h2>New Thread</h2>
#using (Html.BeginForm()) {
#Html.AntiForgeryToken()
#Html.ValidationSummary(true)
<fieldset>
<div class="editor-field">
#Html.HiddenFor(model => model.TH_ID)
</div>
<div class="editor-label">
TOPIC
</div>
<div class="editor-field">
#Html.EditorFor(model => model.TOPIC)
#Html.ValidationMessageFor(model => model.TOPIC)
</div>
<div class="editor-label">
CATEGORY
</div>
<div class="editor-field">
#Html.EditorFor(model => model.ForumCategory)
</div>
<p>
<input type="submit" value="Create" />
</p>
</fieldset>
}
And PartialView for ForumCategory:
ForumCategory
#model AnimeWeb.Models.FORUMCATEGORY
#Html.HiddenFor(model => model.CA_ID)
#Html.HiddenFor(model => model.CATEGORY)
<div>
#Html.DropDownListFor(item => Model.CA_ID, ViewBag.CA_ID as SelectList, "-- Select --")
</div>
ForumController
public ActionResult Create()
{
var db = new MainDatabaseEntities();
var viewModel = new ForumThread
{
ForumCategory = db.ForumCategory.Select(c => new { CA_ID = c.CA_ID, CATEGORY = c.CATEGORY, isSelected = false }).ToList().Select(g => new ForumCategory
{
CA_ID = g.CA_ID,
CATEGORY = g.CATEGORY,
isSelected = false
}).ToList(),
};
return View(viewModel);
}
//
// POST: /Forum/Create
[HttpPost]
public ActionResult Create(ForumThread forumthread, String user, int id)
{
var db = new MainDatabaseEntities();
var newthread = new ForumThread
{
TH_ID = forumthread.TH_ID,
DATE = DateTime.Now,
TOPIC = forumthread.TOPIC,
USER = forumthread.USER,
ForumCategory = new List<ForumCategory>()
};
foreach (var selectedCategory in forumthread.FORUMCATEGORY.Where(c => c.isSelected))
{
var category = new ForumCategory { CA_ID = selectedCategory.CA_ID };
db.ForumCategory.Attach(category);
newthread.ForumCategory.Add(category);
}
db.ForumThread.Add(newthread);
db.SaveChanges();
return RedirectToAction("Index");
}
And it obviously doesn't work. I tried to use other threads on this forum but nothing helped. Could someone explain me how to make this work?
The error is in partial view of ForumCategory:
The ViewData item that has the key 'CA_ID' is of type 'System.Int32' but must be of type 'IEnumerable<SelectListItem>'.
In your PartialView for ForumCategory, your cast is not correct:
#Html.DropDownListFor(item => Model.CA_ID, ViewBag.CA_ID as SelectList, "-- Select --")
You have to use a SelectList (List of SelectListItem) that you can implement for example in a method in your model:
public List<SelectListItem> GetCategories()
{
var db = new MainDatabaseEntities();
List<SelectListItem> list = new List<SelectListItem>();
// Add empty item if needed
SelectListItem commonItem = new SelectListItem();
commonItem.Text = "--- Select ---";
commonItem.Value = "-1";
commonItem.Selected = true;
list.Add(commonItem);
// Add items from Database
foreach (ForumCategory fc in db.ForumCategory)
{
SelectListItem i = new SelectListItem();
i.Text = fc.CATEGORY;
i.Value = fc.CA_ID.ToString();
list.Add(i);
}
return list;
}
And then you can have you dropdown like that:
#Html.DropDownList("DropName", Model.GetCategories())
There may be other errors in some parts of your code, I just answered to the one you quoted
In your editortemplate, you have:
ViewBag.CA_ID as SelectList
But you don't show where you fill the ViewBag. Instead you might want to do something like this:
#Html.DropDownListFor(m => m.CA_ID,
new SelectList(Model.ForumCategory,
"CA_ID", "CATEGORY", Model.CA_ID))
As also explained in MVC3 DropDownListFor - a simple example?.

Posting unknown amount of fields

First off, let me say that I'm new to MVC so if something seem strange then that's why. I probobly don't have the right aproach so I would like some guidelines
I have a form from which the user is suppose to enter translations to a page. The translations are handled as a list of TranslationObjects to the page object. I need a way to render input fields in the form for each language branch represented on the site and let the user fill those out and post them back to the controller.
This example is just a simplification of a more complex model, but it explains the problem pretty well.
The viewmodel:
public class Page
{
public List<TranslationObject> Translation { get; set; }
}
public class TranslationObject
{
public string LanguageBranch { get; set; }
public string PageName { get; set; }
public string PageDescription { get; set; }
}
Controller for rendering the form:
public ActionResult AddPage()
{
var model = new Page {Translation = new List<TranslationObject>()};
foreach (var languageBranch in new[] {"en", "sv", "de"}) // These are normally loaded from database
{
model.Translation.Add(new TranslationObject{ LanguageBranch = languageBranch});
}
return View(model);
}
View:
#model MvcApplication1.Models.Page
#using (Html.BeginForm("SubmitPage", "Home", FormMethod.Post))
{
foreach (var translation in Model.Translation)
{
#Html.LabelFor(x => x.Translation.FirstOrDefault(y => y.LanguageBranch == translation.LanguageBranch).PageName)
#Html.TextBoxFor(x => x.Translation.FirstOrDefault(y => y.LanguageBranch == translation.LanguageBranch).PageName)<br/>
#Html.LabelFor(x => x.Translation.FirstOrDefault(y => y.LanguageBranch == translation.LanguageBranch).PageDescription)
#Html.TextBoxFor(x => x.Translation.FirstOrDefault(y => y.LanguageBranch == translation.LanguageBranch).PageDescription)
}
<input type="submit" value="Submit"/>
}
And controller for the post:
[HttpPost]
public ActionResult SubmitPage(Page model)
{
// Save model to db
return View();
}
The translation object of the Page model is always null. I'm aware that this is probobly the wrong aproach so I'm asking for some direction to render a list of objects and returning them to the controller when posting
Use for instead of foreach.
#model MvcApplication1.Models.Page
#using (Html.BeginForm("SubmitPage", "Home", FormMethod.Post))
{
for(int i=0;i<Model.Translation.Count;i++)
{
#Html.LabelFor(x => x.Translation[i].PageName)
#Html.TextBoxFor(x => x.Translation[i].PageName)<br/>
#Html.LabelFor(x => x.Translation[i].PageDescription)
#Html.TextBoxFor(x => x.Translation[i].PageDescription)
}
<input type="submit" value="Submit"/>
}
And in Page Constructor initialize the Translation

Retrieving dynamic checkbox values and label from strongly typed view in MVC3

EDIT: whoever fixed my question so the code displayed right, thank you! :)
I have a silly problem I really need som help with, after a whole day of googling I'm close to loosing my head!
I am working with the asp.net membership, and I want to be able to set roles on user once the web app is upp and running.
To implement this I am sending a model to a strongly typed view with a list of checkboxes, if the user is in one of the roles in the list the checkbox is checked. (this part works)
But I cant figure out how to return the checkbox values in the Edit method, as the model returns 'null' on the List-property.
I am sure I've missed something obvious here, and would be very happy for any help...
And how can I add code to this question? I cant get the formatting right...
View
#model Mvc4m.Models.UserRoles
#{
ViewBag.Title = "Edit";
}
<script src="#Url.Content("~/Scripts/jquery.validate.min.js")" type="text/javascript"> </script>
<script src="#Url.Content("~/Scripts/jquery.validate.unobtrusive.min.js")" type="text/javascript"></script>
#using (Html.BeginForm()) {
#Html.ValidationSummary(true)
<fieldset>
<legend>UserRoles</legend>
<div class="editor-label">
<h2> #Html.DisplayFor(model => model.Name)</h2>
</div>
<div class="editor-label">
#Html.LabelFor(model => model.Roles)
</div>
<div class="editor-field" id="testar">
#foreach (var model in Model.AllRolles)
{
#Html.CheckBoxFor(item => model.IsActive, model.Role)
#Html.Label(model.Role)
}
</div>
<div class="editor-label">
#Html.LabelFor(model => model.IsApproved)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.IsApproved)
#Html.ValidationMessageFor(model => model.IsApproved)
</div>
<p>
<input type="submit" value="Save" />
</p>
</fieldset>
}
<div>
#Html.ActionLink("Back to List", "Index")
</div>
Controller
public List<AllRolles> UserActiveRoles(string name)
{
var list = new List<AllRolles>();
foreach(var role in Roles.GetAllRoles())
{
var hej = new AllRolles()
{
Role = role,
IsActive = Roles.IsUserInRole(name,role)
};
list.Add(hej);
}
return list;
}
public ActionResult Index()
{
var roles = Roles.GetAllRoles();
var users = from MembershipUser u in Membership.GetAllUsers()
select new UserRoles()
{
Name = u.UserName,
AllRolles = UserActiveRoles(u.UserName)
};
return View(users);
}
public ActionResult Edit(string name)
{
var user = Membership.GetUser(name);
var model = new UserRoles()
{
Name = user.UserName,
AllRolles = UserActiveRoles(name)
};
return View(model);
}
//
// POST: /Admin/ManageUsers/Edit/5
[HttpPost]
public ActionResult Edit(UserRoles user)
{
var name = user.Name;
foreach (var role in user.AllRolles)
{
if (role.IsActive == true)
Roles.AddUserToRole(name,role.Role);
else
Roles.RemoveUserFromRole(name,role.Role);
}
return RedirectToAction("Index");
}
Models
public class UserRoles
{
public string Name { get; set; }
public List<string> Roles { get; set; }
public bool IsApproved { get; set; }
public List<AllRolles> AllRolles { get; set; }
}
namespace Mvc4m.Areas.Admin.Models
{
public class AllRolles
{
public string Role { get; set; }
public bool IsActive { get; set; }
}
}
At UserRoles.cs
Change
public List<AllRolles> AllRolles { get; set; }
to
public AllRolles[] AllRolles { get; set; }
At Edit.cshtml
Change
#foreach (var model in Model.AllRolles)
{
#Html.CheckBoxFor(item => model.IsActive, model.Role)
#Html.Label(model.Role)
}
to
#for (int i = 0; i < Model.AllRolles.Length; i++)
{
#Html.CheckBoxFor(item => Model.AllRolles[i].IsActive)
#Html.HiddenFor(item => Model.AllRolles[i].Role)
#Html.Label(Model.AllRolles[i].Role)
}
At your controller
Add .ToArray() after UserActiveRoles(u.UserName)
public ActionResult Index()
{
var roles = Roles.GetAllRoles();
var users = from MembershipUser u in Membership.GetAllUsers()
select new UserRoles()
{
Name = u.UserName,
AllRolles = UserActiveRoles(u.UserName).ToArray()
};
return View(users);
}
The problem
You must show to the ModelBinder that you're sending a collection, instead of a bunch of parameters.
What's sent to the server?
AllRolles[0].IsActive:false
AllRolles[0].Role:Admin
AllRolles[1].IsActive:false
AllRolles[1].Role:User
Hope it helps
It's really simple. You must give your checkboxes a name let's say myCheckboxes and you pass a int[] myCheckboxes to POST action. I've provided you a code sample:
In your view let's say you have
#foreach (var role in Model.AllRoles)
{
<input type="checkbox" name="UserNotInRoles" value="#role.RoleId" checked/>#role.RoleName
<br />
}
#foreach (var role in Model.UserRole)
{
<input type="checkbox" name="UserInRoles" value="#role.RoleId" checked/>#role.RoleName
<br />
}
Now to post what user has checked/unchecked you use the following:
[HttpPost]
public ActionResult Edit(UserRoleSaveVM saveRoles)
{
if (saveRoles.UserNotInRoles != null)
{
foreach (int roleID in saveRoles.UserNotInRoles)
{
//DO SOMETHING
}
}
where UserRoleSaveVM is
public class UserRoleSaveVM
{
public int UserID { get; set; }
public int[] UserInRoles { get; set; } //note that property name is the same as checkbox name
public int[] UserNotInRoles{ get; set; } //note that property name is the same as checkbox name
}

MVC 3 validation messages not shown due ModelState lost after PartialView

I have an MVC 3 project where I have 1 view LoginRegister which contains 2 views with forms for login and pre-register. The problem is after incorrectly completing the pre-register form and using PartialView("LoginRegister", loginRegisterViewModel) validation messages aren't displayed due the ModelState being lost. Before reading the next paragraph it's probably best to jump to the CODE.
Debugging the PartialView("LoginRegister", loginRegisterViewModel) and stepping into the PreRegister view to the following #Html.ErrorMessageFor(model => model.Email). Where the ModelState does not contain the Email key (see below) and hence doesn't display an error message.
private static MvcHtmlString ErrorMessageHelper(this HtmlHelper htmlHelper, ModelMetadata modelMetadata, string expression, string validationMessage, IDictionary<string, object> htmlAttributes)
{
string modelName = htmlHelper.ViewContext.ViewData.TemplateInfo.GetFullHtmlFieldName(expression);
if (!htmlHelper.ViewData.ModelState.ContainsKey(modelName)) // ModelState contains no keys, e.g. Email not found and a null is returned
{
return null;
}
// code continues here to render error message
}
CODE
ViewModels
LoginRegisterViewModel
namespace Site.ViewModels.Account
{
public class LoginRegisterViewModel
{
public bool IsDialog { get; set; }
public PreRegisterViewModel PreRegister { get; set; }
public QuickLoginViewModel QuickLogin { get; set; }
}
}
PreRegisterViewModel
using FluentValidation.Attributes;
using Site.ViewModels.Validators;
namespace Site.ViewModels.Account
{
[Validator(typeof(PreRegisterViewModelValidator))]
public class PreRegisterViewModel
{
public string Email { get; set; }
public string ConfirmEmail { get; set; }
}
}
Views
LoginRegister
#model Site.ViewModels.Account.LoginRegisterViewModel
#{
Layout = ViewBag.Layout;
}
<div id="loginReg" class="mod-loginReg">
<h2>Login and Registration</h2>
#{Html.RenderPartial("PreRegister", Model.PreRegister, new ViewDataDictionary());}
#{Html.RenderPartial("QuickLogin", Model.QuickLogin, new ViewDataDictionary());}
</div>
PreRegister
#using Site.Classes.MVC.Extensions;
#model Site.ViewModels.Account.PreRegisterViewModel
#using (Html.BeginForm("PreRegister", "Account", FormMethod.Post, new { #class = "form-errorContainer" }))
{
<div class="mod-loginReg-register">
<h3>Register</h3>
<p>Please enter your email address to register.<br /><br /></p>
<div class="mod-loginReg-form">
<div class="field-item">
#Html.LabelFor(model => model.Email)
#Html.TextBoxFor(model => model.Email, new { #maxlength = "255", #class = "Textbox required email" })
#Html.ErrorMessageFor(model => model.Email)
</div>
<div class="field-item">
#Html.LabelFor(model => model.ConfirmEmail)
#Html.TextBoxFor(model => model.ConfirmEmail, new { #maxlength = "255", #class = "Textbox required email" })
#Html.ErrorMessageFor(model => model.ConfirmEmail)
</div>
<div class="button-group">
#Html.Button("Register", "Register")
</div>
</div>
</div>
}
AccountController
[RequireHttps]
public ActionResult LoginRegister(int showDialog)
{
var loginRegisterViewModel = new LoginRegisterViewModel();
if (showDialog == 1)
{
ViewBag.Layout = "~/layouts/structure/dialog.cshtml";
loginRegisterViewModel.IsDialog = true;
}
else
{
ViewBag.Layout = "~/layouts/structure/2column.cshtml";
}
return PartialView(loginRegisterViewModel);
}
[RequireHttps]
public ActionResult PreRegister()
{
return PartialView();
}
[HttpPost]
[RequireHttps]
public ActionResult PreRegister(PreRegisterViewModel model)
{
if (ModelState.IsValid)
{
return PartialView("PreRegisterComplete");
}
var loginRegisterViewModel = new LoginRegisterViewModel { PreRegister = model, QuickLogin = new QuickLoginViewModel() };
return PartialView("LoginRegister", loginRegisterViewModel);
}
This is caused by the viewData being cleared out when new ViewDataDictionary() is called.
#{Html.RenderPartial("PreRegister", Model.PreRegister, new ViewDataDictionary());}
#{Html.RenderPartial("QuickLogin", Model.QuickLogin, new ViewDataDictionary());}
According to MSDN ViewDataDictionary is used for:
Represents a container that is used to pass data between a controller
and a view.
I assume you have a good reason for this otherwise you would not have taken the trouble to add the additional parameter.
If you change it to:
#Html.RenderPartial("PreRegister", Model.PreRegister)
#Html.RenderPartial("QuickLogin", Model.QuickLogin)
the ModelState built up in the controller should be available on your views.

Categories