ASP.NET MVC 2 Model with array/list - asp.net-mvc-2

I am in the process of creating my first site in ASP.NET MVC, it is a kind of learn as you go. But I have hit a problem that I just can't find a solution for.
I want my user to be able to create an album with songs and tags attached.
That can be an unspecified number of songs and tags. But there must be a minimum of 5 songs and 2 tags.
But I can not figure out how to make this possible through the model, here is how far I have been able to get.
public class AlbumCreateModel
{
[Required]
[DisplayName("Title")]
public string Title { get; set; }
[DisplayName("Description")]
public string Description { get; set; }
[DisplayName("Publish")]
public bool Public { get; set; }
[DisplayName("Tags")]
// Min 2 tags no max
public List<AlbumTagModel> Tags { get; set; }
[DisplayName("Songs")]
// Min 5 songs no max
public List<AlbumSongModel> Songs { get; set; }
}
public class AlbumTagModel
{
[Required]
[DisplayName("Tag")]
// Regex to test no spaces
// min 2 characters
// maximum 15 characters
public string Tag { get; set; }
}
public class AlbumSongModel
{
[Required]
[DisplayName("Title")]
public string Title { get; set; }
[Required]
[DisplayName("Artist")]
public string Artist { get; set; }
[DisplayName("Description")]
public string Description { get; set; }
[DisplayName("Song Length")]
public double Length { get; set; }
[DisplayName("Year")]
public int Description { get; set; }
}
View:
<%# Page Title="" Language="C#" MasterPageFile="~/App/Views/Shared/MasterPage.Master" Inherits="System.Web.Mvc.ViewPage<album.App.Models.AlbumCreateModel>" %>
<asp:Content ID="Content1" ContentPlaceHolderID="TitleContent" runat="server">
Create
</asp:Content>
<asp:Content ID="Content2" ContentPlaceHolderID="MainContent" runat="server">
<% using (Html.BeginForm()) { %>
<%: Html.ValidationSummary(true, "Committing the album was unsuccessful. Please correct the errors and try again.")%>
<div>
<fieldset>
<legend>Album Information</legend>
<div class="editor-label">
<%: Html.LabelFor(m => m.Title) %>
</div>
<div class="editor-field">
<%: Html.TextBoxFor(m => m.Title)%>
<%: Html.ValidationMessageFor(m => m.Title)%>
</div>
<div class="editor-label">
<%: Html.LabelFor(m => m.Description) %>
</div>
<div class="editor-field">
<%: Html.TextAreaFor(m => m.Description)%>
<%: Html.ValidationMessageFor(m => m.Description)%>
</div>
<!-- Tags here -->
<!-- Songs here -->
<p>
<input type="submit" value="Commit" />
</p>
</fieldset>
</div>
<% } %>
</asp:Content>
<asp:Content ID="Content3" ContentPlaceHolderID="MetaData" runat="server">
</asp:Content>
A possible solution:
Model:
public class PlaylistModel
{
[Required]
[DisplayName("Title")]
public string Title { get; set; }
[DisplayName("Description")]
public string Description { get; set; }
[DisplayName("Publish")]
public bool Public { get; set; }
[DisplayName("Tags")]
[ListCount(Min = 2)]
// Min 2 tags no max
public List<PlaylistTagModel> Tags { get; set; }
[DisplayName("Songs")]
[ListCount(Min = 5)]
public List<PlaylistSongModel> Songs { get; set; }
}
public class PlaylistTagModel
{
[Required]
[DisplayName("Tag")]
// Regex to test no spaces
// min 2 characters
// maximum 15 characters
public string Tag { get; set; }
}
public class PlaylistSongModel
{
[Required]
[DisplayName("Title")]
public string Title { get; set; }
[Required]
[DisplayName("Artist")]
public string Artist { get; set; }
[DisplayName("Description")]
public string Description { get; set; }
[DisplayName("Song Length")]
public int Length { get; set; }
[DisplayName("Year")]
public int Year { get; set; }
}
View:
<%# Page Title="" Language="C#" MasterPageFile="~/App/Views/Shared/MasterPage.Master" Inherits="System.Web.Mvc.ViewPage<playlist.App.Models.PlaylistModel>" %>
<asp:Content ID="Content1" ContentPlaceHolderID="TitleContent" runat="server">
Create
</asp:Content>
<asp:Content ID="Content2" ContentPlaceHolderID="MainContent" runat="server">
<% using (Html.BeginForm()) { %>
<%: Html.ValidationSummary(true, "Committing the playlist was unsuccessful. Please correct the errors and try again.")%>
<div>
<fieldset>
<legend>Playlist Information</legend>
<div class="editor-label">
<%: Html.LabelFor(m => m.Title) %>
</div>
<div class="editor-field">
<%: Html.TextBoxFor(m => m.Title)%>
<%: Html.ValidationMessageFor(m => m.Title)%>
</div>
<div class="editor-label">
<%: Html.LabelFor(m => m.Description) %>
</div>
<div class="editor-field">
<%: Html.TextAreaFor(m => m.Description)%>
<%: Html.ValidationMessageFor(m => m.Description)%>
</div>
<br />
<%: Html.ValidationMessageFor(m => m.Tags)%>
<div class="editor-label">
<%: Html.LabelFor(m => m.Tags)%>
</div>
<div class="editor-field">
<%: Html.EditorFor(m => m.Tags) %>
<%: Html.Editor("Tags[" + (Model == null ? 0 : Model.Tags.Count) + "]", "PlaylistTagModel")%>
</div>
<br />
<%: Html.ValidationMessageFor(m => m.Songs)%>
<div class="editor-label">
<%: Html.LabelFor(m => m.Songs)%>
</div>
<div class="editor-field">
<%: Html.EditorFor(m => m.Songs)%>
<%: Html.Editor("Songs[" + (Model == null ? 0 : Model.Songs.Count) + "]", "PlaylistSongModel")%>
</div>
<p>
<input type="submit" value="Commit" />
</p>
</fieldset>
</div>
<% } %>
</asp:Content>
<asp:Content ID="Content3" ContentPlaceHolderID="MetaData" runat="server">
</asp:Content>
The two templates:
<%# Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<playlist.App.Models.PlaylistSongModel>" %>
<fieldset>
<legend>Song Information</legend>
<%: Html.ValidationSummary(true, "Committing this song was unsuccessful. Please correct the errors and try again.")%>
<div class="editor-label">
<%: Html.LabelFor(m => m.Title) %>
</div>
<div class="editor-field">
<%: Html.TextBoxFor(m => m.Title)%>
<%: Html.ValidationMessageFor(m => m.Title)%>
</div>
<div class="editor-label">
<%: Html.LabelFor(m => m.Artist)%>
</div>
<div class="editor-field">
<%: Html.TextBoxFor(m => m.Artist)%>
<%: Html.ValidationMessageFor(m => m.Artist)%>
</div>
<div class="editor-label">
<%: Html.LabelFor(m => m.Description)%>
</div>
<div class="editor-field">
<%: Html.TextAreaFor(m => m.Description)%>
<%: Html.ValidationMessageFor(m => m.Description)%>
</div>
<div class="editor-label">
<%: Html.LabelFor(m => m.Length)%>
</div>
<div class="editor-field">
<%: Html.TextBoxFor(m => m.Length)%>
<%: Html.ValidationMessageFor(m => m.Length)%>
</div>
<div class="editor-label">
<%: Html.LabelFor(m => m.Year)%>
</div>
<div class="editor-field">
<%: Html.TextBoxFor(m => m.Year)%>
<%: Html.ValidationMessageFor(m => m.Year)%>
</div>
</fieldset>
Tag:
<%# Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<playlist.App.Models.PlaylistTagModel>" %>
<span class="tag"><%: Html.TextBoxFor(m => m.Tag)%></span> <%: Html.ValidationMessageFor(m => m.Tag)%>
And finally my custom validator to lists:
public class ListCountAttribute : ValidationAttribute
{
public int Min { get; set; }
public int Max { get; set; }
public override bool IsValid(object value)
{
if (Min == 0 && Max == 0)
return true;
if (value == null)
return false;
if (!(value is ICollection))
throw new InvalidOperationException("ListCountAttribute requires underlying property to implement ICollection");
ICollection countable = value as ICollection;
if (Min == 0 && Max != 0)
return countable.Count <= Max;
else if (Max == 0 && Min != 0)
return countable.Count >= Min;
return (countable.Count >= Min) && (countable.Count <= Max);
}
public override string FormatErrorMessage(string name)
{
if (Min == 0 && Max != 0)
return "The field set " + name + " can not be larger then " + Max;
else if (Max == 0 && Min != 0)
return "The field set " + name + " need to have atleast a count of " + Min;
return "The field set " + name + " need to between or equal to " + Min + " and " + Max;
}
}

Create a folder in /views/shared called "EditorTemplates".
It must be called this exactly.
In this folder create files called AlbumTagModel.ascx and AlbumSongModel.ascx strongly typed to their respective models.
Add the input fields in these files but do not wrap them in form tags.
Back in your view page put:
<%: Html.EditorFor(m => m.Tags)%>
and
<%: Html.EditorFor(m => m.Songs)%>
Now voila!
When you render the input tags will be subscripted to match your list. EditorFor will loop and render all by itself.
When you post your strongly typed AlbumViewModel your lists will be correctly bound back to their original positions.
To add new songs/tags add the following to your AlbumViewModel:
public AlbumTagModel NewTagModel {get;set;}
and add an extra EditorFor() for it.
When your model posts if the NewTagModel is valid add it to the list and reshow the view.

You have to write a custom validation attribute to support that requirement.
Here's an example that isn't exactly what you're looking for, but should get you pointed in the right direction. There are notes after it on how you would adjust it for your environment.
public class AtLeastOneRequiredAttribute : ValidationAttribute
{
public override bool IsValid(object value)
{
if (value == null)
return false;
if (!(value is ICountable))
throw new InvalidOperationException("AtLeastOneRequiredAttribute requires underlying property to implement ICountable");
ICountable countable = value as ICountable;
return countable.Count >= 1;
}
}
We use our own "child models" instead of generic lists and have them implement ICountable, which is a utility interface in our environment. You would just check to make sure that your value implemented IList, then invoke (value as IList).Count.
For a general minimum instead of the "At Least One", define a Min Property.
Hope that gets you headed in the right direction, post if you have other questions.

I can't seem to make it work when trying to use template editors for editing a collection of objects.
Here's what I have:
public class Candidate
{
public Candidate()
{
this.References = new List<Reference>();
}
public int Id { get; set; }
public string Name { get; set; }
public DateTime? DateOfBirth { get; set; }
[UIHint("Reference")]
public List<Reference> References { get; set; }
}
public class Reference
{
public int Id { get; set; }
public string Name { get; set; }
public string Institution { get; set; }
public string Email { get; set; }
public string Phone { get; set; }
public string Position { get; set; }
}
I created a template in Views/Shared/TemplateEditors, and created a strongly typed partial view Register.ascx.
<%# Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<Models.Reference>" %>
<%:Html.TextBoxFor(model=>model.Name) %>
<%:Html.TextBoxFor(model=>model.Email) %>
<%:Html.TextBoxFor(model=>model.Institution) %>
<%:Html.TextBoxFor(model=>model.Position) %>
<%:Html.TextBoxFor(model=>model.Phone) %>
In the view that I have the candidate I use the code below to render the list of references.
<%: Html.EditorFor(m => m.References)%>
When I run the project I get
InvalidOperationException: The model item passed into the dictionary is of type 'System.Collections.Generic.List`1[Models.Reference]', but this dictionary requires a model item of type 'Models.Reference'.
Do you guys see anything wrong with the implementation?
Thanks.

Related

Problem with validating a viewmodel using fluent validation

I am trying to validate a viewmodel using fluent validation. When i post the viewmodel object, the modelstate.isvalid always returns false. When i have checked the values on the posted object, the properties which were used to get the data to be shown in dropdowns are also being validated. How to resolve this. Am i doing it in a wrong way, pls help.
I'm new to ASP.net MVC and just trying out using fluent validation and fluent NHibernate mappings in this project.
More details as follows:
I have a domain model object as below:
[Validator(typeof(TestRequirementValidator))]
public class TestRequirement
{
public virtual int Id { get; private set; }
public virtual int SampleId { get; set; }
public virtual int TestId { get; set; }
public virtual int StandardId { get; set; }
public virtual string Description { get; set; }
public virtual Sample Sample { get; set; }
public virtual Test Test { get; set; }
public virtual Standard Standard { get; set; }
}
I have created a view model as below:
[Validator(typeof(TestRequirementViewModelValidator))]
public class TestRequirementViewModel
{
public TestRequirement TestRequirement;
public SelectList Samples;
public SelectList Tests;
public SelectList Standards;
public TestRequirementViewModel()
{
ISession _session = FNHsessionFactory.GetSessionFactory();
this.TestRequirement = new TestRequirement();
this.Samples = new SelectList(from S in _session.Linq<Sample>() select S, "Id", "Name");
this.Tests = new SelectList(from T in _session.Linq<Test>() select T, "Id", "Name");
this.Standards = new SelectList(from St in _session.Linq<Standard>() select St, "Id", "Name");
}
}
Model Validator is as below:
public class TestRequirementValidator : AbstractValidator<TestRequirement>
{
public TestRequirementValidator()
{
RuleFor(x => x.SampleId)
.NotEmpty()
.WithMessage("This field is required")
.DisplayName("Sample Name");
RuleFor(x => x.TestId)
.DisplayName("Test Name");
RuleFor(x => x.StandardId)
.NotEmpty()
.WithMessage("This field is required")
.DisplayName("Standard Name");
RuleFor(x => x.Description)
.NotEmpty()
.WithMessage("This field is required")
.Length(0, 10)
.WithMessage("Length of this field cannot be more than 10 characters");
}
}
View model validator is as below:
public class TestRequirementViewModelValidator:AbstractValidator-TestRequirementViewModel-
{
public TestRequirementViewModelValidator()
{
RuleFor(x => x.TestRequirement)
.SetValidator(new TestRequirementValidator());
}
}
View is as below:
<%# Page Language="C#" Inherits="System.Web.Mvc.ViewPage<foo.Models.ViewModels.TestRequirementViewModel>" MasterPageFile="~/Views/shared/site.master" %>
<asp:Content ContentPlaceHolderID="MainContent" ID="MainContentContent" runat="server">
<h3><%= Html.Encode(ViewData["Message"]) %></h3>
<% using (Html.BeginForm()) {%>
<%= Html.ValidationSummary(true) %>
<fieldset>
<legend>Create Test Requirement</legend>
<div class="editor-label">
<%= Html.LabelFor(model => model.TestRequirement.SampleId) %>
</div>
<div class="editor-field">
<%= Html.DropDownListFor(model => model.TestRequirement.SampleId, new SelectList(Model.Samples.Items, Model.Samples.DataValueField, Model.Samples.DataTextField), "Select Sample") %>
<%= Html.ValidationMessageFor(model => model.TestRequirement.SampleId) %>
</div>
<div class="editor-label">
<%= Html.LabelFor(model => model.TestRequirement.TestId) %>
</div>
<div class="editor-field">
<%= Html.DropDownListFor(model => model.TestRequirement.TestId, new SelectList(Model.Tests.Items, Model.Tests.DataValueField, Model.Tests.DataTextField), "Select Test") %>
<%= Html.ValidationMessageFor(model => model.TestRequirement.TestId) %>
</div>
<div class="editor-label">
<%= Html.LabelFor(model => model.TestRequirement.StandardId) %>
</div>
<div class="editor-field">
<%= Html.DropDownListFor(model => model.TestRequirement.StandardId, new SelectList(Model.Standards.Items, Model.Standards.DataValueField, Model.Standards.DataTextField), "Select Standard") %>
<%= Html.ValidationMessageFor(model => model.TestRequirement.StandardId) %>
</div>
<div class="editor-label">
<%= Html.LabelFor(model => model.TestRequirement.Description) %>
</div>
<div class="editor-field">
<%= Html.TextBoxFor(model => model.TestRequirement.Description) %>
<%= Html.ValidationMessageFor(model => model.TestRequirement.Description) %>
</div>
<p>
<input type="submit" value="Create" />
</p>
</fieldset>
<% } %>
<%= Html.ClientSideValidation<TestRequirement>("TestRequirement") %>
</asp:Content>
Controller is as below:
public ActionResult TestRequirement()
{
TestRequirementViewModel NewTestRequirement = new TestRequirementViewModel();
return View(NewTestRequirement);
}
[HttpPost]
public ActionResult TestRequirement(TestRequirementViewModel NewTestRequirement)
{
if(ModelState.IsValid)
{
ISession _session = FNHsessionFactory.GetSessionFactory();
_session.SaveOrUpdate(NewTestRequirement.TestRequirement);
ViewData["Message"] = "New Test Requirement has been created successfully";
return View();
}
return View(NewTestRequirement);
}
I can't help with Fluent Validation, but as you've tagged this as fluent-nhibernate I thought I should comment on your NHibernate usage.
Your view model should not be using NHibernate in its constructor; in fact, your view model should just be a data structure that gets populated by an external service. Similarly, you might want to do the same with your controller; it's common for people to extract data access into a repository to isolate their controllers (and make testing easier, you are testing aren't you?).
If you're using a repository, you can then project your view model from your entity; you can do that either by using NHibernate projections and transformers or by using a tool like AutoMapper.

DisplayName metadata does not show up on view

How will I show the correct DsiplayName on my view considering the following model.
<%# Page Title="" Language="C#" MasterPageFile="~/Views/Shared/Site.Master" Inherits="System.Web.Mvc.ViewPage<Project.Models.RegisterViewModel>" %>
<asp:Content ID="Content1" ContentPlaceHolderID="MainContent" runat="server">
<% using (Html.BeginForm())
{%>
<table>
<tr>
<td class="label">
<%: Html.LabelFor(model => model.User.UserPrimaryEmail)%>
</td>
<td class="field">
<%: Html.TextBoxFor(model => model.User.UserPrimaryEmail)%>
</td>
<td class="field-error">
<div class="field-error-msg">
<%: Html.ValidationMessageFor(model => model.User.UserPrimaryEmail)%>
</div>
</td>
</tr>
</table>
</asp:Content>
public class RegisterViewModel
{
public User User { get; set; }
}
[MetadataType(typeof(UserMetaData))]
public partial class User : UserBase
{
//Inherits from Generated Class UserBase
//to set default values here for the constructor
// Not used except as a source of metadata
public class UserMetaData
{
[Required]
[DisplayName("Email Login")]
[DataType(DataType.EmailAddress)]
[Email(ErrorMessage = "Invalid Email")]
public string UserPrimaryEmail { get; set; }
}
}
The form does not display "Email Login" but "UserPrimaryEmail"
You View is strongly typed to Project.Models.RegisterViewModel, however you are adding your Data Annotations to the User Object.
Generally you would have:
public class RegisterViewModel
{
[Required]
[DisplayName("Email Login")]
[DataType(DataType.EmailAddress)]
[Email(ErrorMessage = "Invalid Email")]
public String Email { get; set; }
.....
other properties
}

DisplayName metadata does not show up on view [duplicate]

How will I show the correct DsiplayName on my view considering the following model.
<%# Page Title="" Language="C#" MasterPageFile="~/Views/Shared/Site.Master" Inherits="System.Web.Mvc.ViewPage<Project.Models.RegisterViewModel>" %>
<asp:Content ID="Content1" ContentPlaceHolderID="MainContent" runat="server">
<% using (Html.BeginForm())
{%>
<table>
<tr>
<td class="label">
<%: Html.LabelFor(model => model.User.UserPrimaryEmail)%>
</td>
<td class="field">
<%: Html.TextBoxFor(model => model.User.UserPrimaryEmail)%>
</td>
<td class="field-error">
<div class="field-error-msg">
<%: Html.ValidationMessageFor(model => model.User.UserPrimaryEmail)%>
</div>
</td>
</tr>
</table>
</asp:Content>
public class RegisterViewModel
{
public User User { get; set; }
}
[MetadataType(typeof(UserMetaData))]
public partial class User : UserBase
{
//Inherits from Generated Class UserBase
//to set default values here for the constructor
// Not used except as a source of metadata
public class UserMetaData
{
[Required]
[DisplayName("Email Login")]
[DataType(DataType.EmailAddress)]
[Email(ErrorMessage = "Invalid Email")]
public string UserPrimaryEmail { get; set; }
}
}
The form does not display "Email Login" but "UserPrimaryEmail"
You View is strongly typed to Project.Models.RegisterViewModel, however you are adding your Data Annotations to the User Object.
Generally you would have:
public class RegisterViewModel
{
[Required]
[DisplayName("Email Login")]
[DataType(DataType.EmailAddress)]
[Email(ErrorMessage = "Invalid Email")]
public String Email { get; set; }
.....
other properties
}

MVC2 model binding w/ many-to-many: at the cusp

I'm an inch away from getting my Create form working. My problem is that some data passed into the form in the view model isn't being sent back in the postback.
Models:
Game:
GameID - int (primary key, auto-incr)
GameTitle - nvarchar(100)
ReviewText - text
ReviewScore - smallint
Pros - text
Cons - text
LastModified - Datetime
GenreID - int (foreign key from Genres)
Platform:
PlatformID - int (primary key, auto-incr)
Name - nvarchar(50)
GamePlatform (not visible as an EF 4 entity):
GameID - int (foreign key from Games)
PlatformID - int (foreign key from Platforms)
The view models I'm using:
public class PlatformListing
{
public Platform Platform { get; set; }
public bool IsSelected { get; set; }
}
public class AdminGameReviewViewModel
{
public Game GameData { get; set; }
public List<Genre> AllGenres { get; set; }
public List<PlatformListing> AllPlatforms { get; set; }
}
And the form itself:
<%# Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<HandiGamer.WebUI.ViewModels.AdminGameReviewViewModel>" %>
<p>
<%: Html.Label("Game Title") %>
<%: Html.TextBoxFor(model => Model.GameData.GameTitle) %>
<%: Html.ValidationMessageFor(model => Model.GameData.GameTitle) %>
</p>
<p>
<%: Html.LabelFor(model => Model.GameData.GenreID) %>
<%: Html.DropDownListFor(m => Model.GameData.GenreID, new SelectList(Model.AllGenres, "GenreID", "Name", Model.GameData.GenreID)) %>
</p>
<p>
<%: Html.Label("Platforms") %><br />
<% for(var i = 0; i < Model.AllPlatforms.Count; ++i)
{ %>
<%: Model.AllPlatforms[i].Platform.Name %> <%: Html.CheckBoxFor(p => Model.AllPlatforms[i].IsSelected) %>
<% } %>
</p>
<p>
<%: Html.Label("Review") %>
<%: Html.TextAreaFor(model => Model.GameData.ReviewText) %>
</p>
<p>
<%: Html.Label("Review Score") %>
<%: Html.DropDownListFor(m => Model.GameData.ReviewScore, new SelectList(new int[] {1, 2, 3, 4, 5}))%>
</p>
<p>
<%: Html.LabelFor(model => model.GameData.Pros) %><br />
<%: Html.TextAreaFor(model => model.GameData.Pros) %>
</p>
<p>
<%: Html.LabelFor(model => model.GameData.Cons) %><br />
<%: Html.TextAreaFor(model => model.GameData.Cons) %>
</p>
And, finally, my Create method (barebones as I'm trying to get this to work):
[HttpPost]
public ActionResult CreateReview([Bind(Prefix = "GameData")]Game newGame, [Bind(Prefix = "AllPlatforms")]List<PlatformListing> PlatformList)
{
try
{
foreach(var plat in PlatformList)
{
if (plat.IsSelected == true)
{
newGame.Platforms.Add(plat.Platform);
}
}
newGame.LastModified = DateTime.Now;
_siteDB.Games.AddObject(newGame);
_siteDB.SaveChanges();
// add form data to entities, then save
return RedirectToAction("Index");
}
catch
{
return View();
}
}
The problem is, of course, with my checkboxes. The boolean isSelected is being set fine, but when the data is being posted, the corresponding Platform object is missing. I thought they would automatically be passed back. So, any suggestions on how to pass back the actual Platform part of a PlatformListing?
Regarding getting the checkbox values, how about using the strategy described here?
Regarding passing around entity objects as arguments, I'd say you're letting your data layer get mixed into your view layer, and you should use a dedicated ViewModel.

How to show errors on in the view from a fluentvalidation result in a asp.net mvc 2 app?

I am learning asp.net mvc 2 and fluent validation. My setup is shown after the text here. My problem is that I do not know how to set the errors contained in the res object on the view page in a nice way. How should this be done? As it is now no errors are displayed on the view, but the validation is working quite well. I suspect I have to insert some code where I have written "// Set errors on view" in the code. But what code do I need to put? I was not really able to find any clear answers to this - maybe I am just blind. I am looking forward to your help. Thank you.
My controller:
public class AccountController
{
public ActionResult LogOn()
{
return View();
}
[HttpPost]
public ActionResult LogOn(LogOnModel1 model, string returnUrl)
{
public class LogOnModel1
{
public string UserName { get; set; }
public string Password { get; set; }
public bool RememberMe { get; set; }
}
public class AccountValidator : AbstractValidator<LogOnModel1>
{
public AccountValidator()
{
RuleFor(x => x.UserName).NotNull().WithMessage("Brugernavn skal udfyldes").NotEmpty().WithMessage("Brugernavn skal udfyldes");
RuleFor(x => x.Password).NotNull().WithMessage("Kodeord skal udfyldes").NotEmpty().WithMessage("Kodeord skal udfyldes");
Custom(x => { return Membership.Provider.ValidateUser(x.UserName,x.Password) ? new ValidationFailure(null, "wrong password") : null; });
}
}
FluentValidation.Results.ValidationResult res = new Models.AccountValidator().Validate(model);
if (res.IsValid)
{
FormsService.SignIn(model.UserName, model.RememberMe);
if (!String.IsNullOrEmpty(returnUrl))
{
return Redirect(returnUrl);
}
else
{
return RedirectToAction("Index", "Home");
}
}
else
{
// Set errors on view
}
return View(model);
}
}
My model and validation class:
public class LogOnModel1
{
public string UserName { get; set; }
public string Password { get; set; }
public bool RememberMe { get; set; }
}
public class AccountValidator : AbstractValidator<LogOnModel1>
{
public AccountValidator()
{
RuleFor(x => x.UserName).NotNull().WithMessage("Brugernavn skal udfyldes").NotEmpty().WithMessage("Brugernavn skal udfyldes");
RuleFor(x => x.Password).NotNull().WithMessage("Kodeord skal udfyldes").NotEmpty().WithMessage("Kodeord skal udfyldes");
Custom(x => { return Membership.Provider.ValidateUser(x.UserName,x.Password) ? new ValidationFailure(null, "wrong password") : null; });
}
}
and finally my view:
<%# Page Language="C#" MasterPageFile="~/Views/Shared/Site.Master" Inherits="System.Web.Mvc.ViewPage<TelCountMVC.Models.LogOnModel1>" %>
<asp:Content ID="loginTitle" ContentPlaceHolderID="TitleContent" runat="server">
Log On
</asp:Content>
<asp:Content ID="loginContent" ContentPlaceHolderID="MainContent" runat="server">
<h2>Log On</h2>
<p>
Please enter your username and password. <%: Html.ActionLink("Register", "Register") %> if you don't have an account.
</p>
<% using (Html.BeginForm()) { %>
<%: Html.ValidationSummary(true, "Login was unsuccessful. Please correct the errors and try again.") %>
<div>
<fieldset>
<legend>Account Information</legend>
<div class="editor-label">
<%: Html.LabelFor(m => m.UserName) %>
</div>
<div class="editor-field">
<%: Html.TextBoxFor(m => m.UserName) %>
<%: Html.ValidationMessageFor(m => m.UserName) %>
</div>
<div class="editor-label">
<%: Html.LabelFor(m => m.Password) %>
</div>
<div class="editor-field">
<%: Html.PasswordFor(m => m.Password) %>
<%: Html.ValidationMessageFor(m => m.Password) %>
</div>
<div class="editor-label">
<%: Html.CheckBoxFor(m => m.RememberMe) %>
<%: Html.LabelFor(m => m.RememberMe) %>
</div>
<p>
<input type="submit" value="Log On" />
</p>
</fieldset>
</div>
<% } %>
</asp:Content>
I suspect that you have figured this out ages ago.
You lose model state if you call RedirectToAction. You have to return a view and pass the LogOnModel1 model into it.
Connect the view to your model instead of System.Web.Mvc.ViewPage and then in your controller do something like this:
if(! ModelState.IsValid) {
return View("Index", logOnModel1);
}
And here is a link to Jeremy Skinner's (the creator of Fluent Validation) description of how to set it up with MVC 2.