Using partial classes for validation? - c#

I'm using Entity Framework to create my data objects. Here's what my designer.cs file looks like:
namespace MyApp.WebUI.Models
{
...
[EdmEntityTypeAttribute(NamespaceName="MyAppDBModel", Name="AddressType")]
[Serializable()]
[DataContractAttribute(IsReference=true)]
public partial class AddressType : EntityObject
{
...
}
...
}
I have a file called Validation.cs in which I want to keep all my validation for my entities. Here's what it looks like:
namespace MyApp.WebUI.Models
{
public class Validations
{
...
[MetadataType(typeof(AddressTypesValidation))]
public partial class AddressType
{
}
public class AddressTypesValidation
{
[Required(ErrorMessage = "Address Type name is required.")]
[StringLength(50, ErrorMessage = "Address Type name must be 50 characters or less.")]
public string Name { get; set; }
}
}
}
I have this in my view page:
<% Html.EnableClientValidation(); %>
<% using (Html.BeginForm("Edit", "AddressTypes", FormMethod.Post)) { %>
<div class="editor-label"><%: Html.LabelFor(m => m.Name) %></div>
<div class="editor-field">
<%: Html.TextBoxFor(m => m.Name) %>
<%: Html.ValidationMessageFor(m => m.Name) %>
</div>
<input type="submit" value="Save" />
<% } %>
But my validations aren't loaded. If I try to submit the form with no value for Name, I get an error message saying The value '' is invalid. instead of my error message.
What am I doing wrong?

There is a fundamental flaw with your approach. It is generally understood that using your database objects as view models and having Mvc do its model binding on them is a very bad idea.
Darin has a great answer detailing the issues associated with using domain objects in views.
I think your issues are being caused because you're mix data objects with view models, and to quote Darin
About 60% of the question I am [Darin]
answering on StackOverflow in the
asp.net-mvc tag wouldn't have been
asked if the OP have used a view
model.

I'm not sure if you can have a partial class then have another partial class that is a nested class. Try having the partial you've declared not be a nested class.
Edit:
Just ran a quick test in VS (not the validation part) and you can't one part of a partial class in a nested type and have another part of the partial class as a non-nested type (or nested in a different type).

I may be off the mark here, but I believe that the Required attribute simply means 'not null', and your StringLengthValidator is only checking for an upper bound. It's not failing because you are sending through a string - unfortunately it's the String.Empty.
You're using the overload
[StringLength(int upperBound, [Parameters])]
Instead, try
[StringLength(int lowerBound, int upperBound, [Parameters])]
something like this, if you want minimum length of 1:
[Required(ErrorMessage = "Address Type name is required.")]
[StringLength(1, 50, ErrorMessage = "Address Type...")]
public string Name { get; set; }

Related

How to validate items in Dictionary<string, T> view model property using FluentValidation

I am using a Dictionary<string, T> data structure to store phone numbers in a view model. The user can add or remove them client side before posting them back to the server.
Back story: I am using a Dictionary, because using a List<T> data structure with ASP.NET MVC 5 requires the names of form fields to contain sequential indexes starting with zero, and it becomes a real pain for JavaScript to add or remove those fields on screen without re-sequencing the index values. I found using a dictionary made it very easy. Now I'm doing a proof of concept task to enable dependency injection that allows us to use our NHibernate session to query the database during validations, and use the same session that the controllers and view models use instead of the "singleton" pattern that FluentValidation uses with MVC 5.
When using the [Validator(typeof(T))] attribute above view models, messages appear by the fields just fine, but the validator instances are singletons in the AppDomain, and the NHibernate session used by the validators is not the same one used by the controllers. This causes data to become out of sync during data validations. Validations that check the database start returning unexpected results, because NHibernate is caching so much data on the server, and it effectively has 2 separate caches.
Project setup
ASP.NET MVC 5
.NET Framework 4.5.1 (but we could upgrade)
FluentValidation v8.5.0
FluentValidation.Mvc5 v8.5.0
FluentValidation.ValidatorAttribute v8.5.0
View models
public class PersonForm
{
public PhoneFieldsCollection Phones { get; set; }
}
public class PhoneFieldsCollection
{
public Dictionary<string, PhoneNumberFields> Items { get; set; }
}
public class PhoneNumberFields
{
[Display(Name="Country Code")]
[DataType(DataType.PhoneNumber)]
public string CountryCode { get; set; }
[Display(Name="Phone Number")]
[DataType(DataType.PhoneNumber)]
public string PhoneNumber { get; set; }
[DataType(DataType.PhoneNumber)]
public string Extension { get; set; }
[Display(Name="Type")]
public string TypeCode { get; set; }
}
View model validators
public class PersonFormValidator : AbstractValidator<PersonForm>
{
private readonly IPersonRepository repository;
public PersonFormValidator(IPersonRepository repository)
{
// Later on in proof of concept I will need to query the database
this.repository = repository;
RuleForEach(model => model.Phones)
.SetValidator(new PhoneNumberFieldsValidator());
}
}
public class PhoneNumberFieldsValidator : AbstractValidator<PhoneNumberFields>
{
public PhoneNumberFieldsValidator()
{
RuleFor(model => model.PhoneNumber)
.NotEmpty();
}
}
Controller code to validate the view models:
private bool IsModelStateValid(PersonForm model)
{
// The `repository` field is an IPersonRepository object from the DI container
var validator = new PersonFormValidator(repository);
var results = validator.Validate(model);
if (results.IsValid)
return true;
results.AddToModelState(ModelState, "");
return false;
}
Razor template code to render the page
Page level template
#model PersonForm
#Html.EditorFor(model => model.Phones)
PhoneFieldCollection editor template
#model PhoneFieldsCollection
<fieldset class="form-group form-group-phones">
<legend class="control-label col-md-3 required">
Phone Numbers:
</legend>
<div class="col-md-9">
#Html.ValidationMessageFor(model => model, "", new { role = "alert", #class = "alert alert-danger", #for = Html.IdFor(model => model) + "-addButton" })
<ol class="list-unstyled">
#foreach (var item in Model.Items)
{
if (item.Value.IsClientSideTemplate)
{
<script type="text/html">
#Html.EditorFor(model => model.Items[item.Key])
</script>
}
else
{
#Html.EditorFor(model => model.Items[item.Key])
}
}
</ol>
<hr />
<p>
<button type="button" class="btn btn-default" id="#Html.IdFor(model => model)-addButton"
data-dynamiclist-action="add"
data-dynamiclist="fieldset.form-group-phones ol">
<span class="glyphicon glyphicon-plus"></span>
Add another phone number
</button>
</p>
</div>
</fieldset>
PhoneNumberFields editor template
#model PhoneNumberFields
#Html.EditorFor(model => model.PhoneNumber)
#Html.ValidationMessageFor(model => model.PhoneNumber)
Required field message not showing up
When I POST the form back to the server with the phone number field empty, I get a validation summary message at the top of the page saying "Phone number field is required", which is what I expect. However, the call to ValidationMessageFor(model => model.PhoneNumber) in the editor template is not causing the validation message to appear by the form field.
When running the application in debug mode I get Phones[0].PhoneNumber for the name of the field that has the validation message, but the name of the field in the view model is Phones.Items[123].PhoneNumber (where 123 is a database Id, or a timestamp generated by new Date().getTime() in JavaScript).
So I know why the validation message isn't showing up next to the field. The challenge is, how can I do this?
How can I validate a Dictionary with FluentValidation so the error messages appear by the form fields when using **ValidationMessageFor(model => model.PhoneNumber) in the editor template?
Update: Looks like there is a GitHub issue from 2017 related to this: Support for IDictionary Validation. The person found a workaround, but the maintainer for FluentValidation basically said supporting this is a monster pain and would require a major refactoring job. I might try fiddling with this myself and posting an answer if I can get something to work.

MVC 5 Generic List Binding to Partial View

I'm having an issue with the bindings of my model to a partial view and feel that there must be a way to do what I want. I wonder if my design is flawed and a small refactoring might be necessary.
A very simplified (and abstract) version of my models would be like so:
Public Class BaseClass
{
Public string Name { get; set; }
Public List<SomeClass> Things { get; set; }
}
Public Class DerivedClass : BaseClass
{
Public List<LineItem> Items { get; set; }
}
Public Class Library
{
Public List<LineItem> Items { get; set; }
}
Public Class LineItem
{
Public string Name { get; set; }
Public string Value { get; set; }
}
I have Editor Templates for the BaseClass, SomeClass, and LineItem. These are shown in the view for DerivedClass and work as intended by submitting changes to the controller. The LineItem template is wrapped in a LineItemList partial view because I intend to use it for a view for Library and don't want to repeat all of that layout and javascript. The LineItemList partial view is included on the DerivedClass view by Html.PartialView since there doesn't seem to be a way to create an Editor Template for the List type. So my views look like this:
DerivedClassView
BaseClassPartialView
SomeClassPartialView
LineItemListPartialView
LineItemParialView
When I submit my form, the controller gets all of the data for the BaseClass and SomeClass list but none for the LineItem list. The difference of course being that one is rendered using Html.EditorFor and the other Html.PartialView.
Refactoring the classes will be tough as they have to be backwards compatible with an old XML format for serialization, but I'm sure I can work some magic if necessary.
As Chris Pratt mentioned, I forgot to include my controller methods:
Public ActionResult DerivedClassEditor()
{
Return View(New DerivedClass());
}
[HttpPost]
Public ActionResult DerivedClassEditor(DerivedClass dc)
{
// Do Stuff
}
I just noticed in the rendered Html, the SomeClass controls are named SomeClass.[0].Name while those of the LineItem are [0].Name. I have a feeling that might be a symptom of the issue.
And my views look similar to this:
DerivedClassEditor
#model DerivedClass
#using (Html.BeginForm())
{
#Html.EditorFor(model => model)
#Html.Partial("LineItemListPartialView")
<input type="submit" />
}
LineItemListPartialView
#model List<LineItem>
<div name="Items">
#Html.EditorFor(model => model)
</div>
LineItemPartialView
#model LineItem
<div name="LineItem">
#Html.LabelFor(model => model.Name)
#Html.TextEditorFor(model => model.Name)
</div>
Edit:
A link to my view: https://github.com/melance/TheRandomizer/blob/Initial/TheRandomizer.WebApp/Views/UserContent/AssignmentEditor.cshtml
I've narrowed down the issue to the fact that when I load one of the lists using #Html.EditorFor it names the inputs Collection[index].Property yet when I add one dynamically using the same call it simply names the input Property. Is there an easy and reusable way to have the addition of new items have the same naming structure?
Crucially, you failed to post your controller code, so I'm stabbing in the dark, but I think I can guess pretty well what's happening. You most likely have something like:
[HttpPost]
public ActionResult MyAwesomeAction(BaseClass model)
{
...
}
And you're assuming that since your view is working with DerivedClass and posting DerivedClass that you should end up with an instance of DerivedClass rather than BaseClass in your action. That assumption is incorrect.
All that exists on post is a set of string key-value pairs. The modelbinder looks at the action signature and attempts to bind the posted data to an instance of the parameter(s), newing up classes as necessary. Given this, the only information the modelbinder has in this scenario is that it's expected to bind values to an instance of BaseClass and a set of data to attempt to do that with. As a result, it will create an instance of BaseClass and bind what data it can, dropping anything it can't. Importantly, since BaseClass doesn't include stuff like your Items property, all of that data will be discarded.
Long and short, polymorphism isn't supported with action parameters. The type of your parameter must be the type you want. Period.
For what it's worth, you can use editor templates for list properties. EditorFor will simply render the editor template for the contained type for each item in the list. For example, calling:
#Html.EditorFor(m => m.Items)
Is essentially the same as:
#for (var i = 0; i < Model.Items.Count(); i++)
{
#Html.EditorFor(m => m.Items[i])
}
So after much research, trial and error and help from Erik, I was able to solve my problem. The issue turned out to be the naming of the form elements in my partial view. When added by the model they are indexed as such: name = "ListProperty[Index].PropertyName". When I was adding my partial views using ajax, the were named for just the Property Name. In order to fix this, I had to handle my ajax calls for the partial view like this:
public ActionResult CreateParameter(Int32 index)
{
ViewData.TemplateInfo.HtmlFieldPrefix = $"Parameters[{index}]";
return PartialView("~/Views/Shared/EditorTemplates/Configuration.cshtml");
}

Passing Interface from View to Controller

I have been trying to figure out how to populate a specfic interface via a form in a view. The interface is in a different project and namespace then that of my controller / view and is automatically generated for storing data in the database:
Interface namespace and Code:
DataAccess.DAL.IVehicle
namespace DataAccess.DAL
{
public partial interface IVehicle
{
String vehicleName { get; set; }
int maxSpeed { get; set; }
}
}
I have a controller which has an action method for receiving information from the form in the view:
Controller Code:
namespace coreproject.Controllers
{
public class NewVehicleController
{
[HttpPost, ValidateInput(false)]
public JsonResult AddVechicle(IVehicle newVehicle)
{
// I expect that newVechicle is populated via the form
}
}
}
I understand that I should be using Html.BeginForm in the view. Below is some code I came up with what I understand would be needed in the view.
View Code:
<%
// This is not working, I am not sure how to tell the view I want the form
// to use the interface located in the following namespace.
#Model DataAccess.DAL.IVehicle;
using (Html.BeginForm("AddVehicle", "NewVechicle", FormMethod.Post))
// Below I understand that I would need some code in the form of Html.EditorFor to
// populate the IVehicle interface in the form. I have seen this as an example:
<%: Html.EditorFor(model => model.VehicleName) %>
<%: Html.EditorFor(model => model.maxSpeed) %>
<%:
}
%>
The questions I have are twofold and are related to the view:
How do I tell the view I want to use an interface located in DataAccess.DAL, which resides in a different project and namespace than the view?
How do I populate the aforementioned interface in the form in order to pass it to the controller?
Any help would be greatly appreciated.
You are mixing a lot of concepts here.
Go to Visual Studio and create a new MVC website.
Run it and see how it works.
Then go on google and lookup the concept of interfaces.
Go back to your newly created MVC website and see the difference to what you have postet here.
Edit:
What you are trying is not possible!
You are asking the MVC framework to create an instance of an interface, this is not possible!
What you must do is to have a concrete class in the Action parameter:
[HttpGet]
public ActionResult AddVechicle()
{
return View(new Vehicle());
}
[HttpPost, ValidateInput(false)]
public JsonResult AddVechicle(Vehicle newVehicle)
{
// I expect that newVechicle is populated via the form
}
you could then declare the "Vehicle" class as follows
public class Vehicle :IVehicle
{
String vehicleName { get; set; }
int maxSpeed { get; set; }
}
I havent testet if the view will accept an interface as a model, you might better change it into the class "Vehicle"
<%
// view name: AddVehicle
// This is not working, I am not sure how to tell the view I want the form
// to use the interface located in the following namespace.
#Model Vehicle;
using (Html.BeginForm("AddVehicle", "NewVechicle", FormMethod.Post))
// Below I understand that I would need some code in the form of Html.EditorFor to
// populate the Vehicle concrete class in the form.
<%: Html.EditorFor(model => model.VehicleName) %>
<%: Html.EditorFor(model => model.maxSpeed) %>
<%:
}
%>

Field Validations in MVC

I would like to enforce field validations on my Views in the MVC app that I am working on. For example -
Limit the length of the field to 40
Ensure only alphanumeric and special characters ##$%&*()-_+][';:?.,! can be entered.
I used the following to restrict the field length:
<div>
<%= Html.TextBoxFor(c => c.CompanyName, new { style = "width:300px", maxlength = "40" })%></div>
How do I ensure that only alphanumeric and special characters can be entered in the textboxes?
EDIT:
I changed the property in my model to
[DataMember(EmitDefaultValue = false)]
[Required(ErrorMessage="CompanyName is Required")]
[StringLength(40, ErrorMessage = "Must be under 40 characters")]
public string CompanyName { get; set; }
To test I tried saving a blank CompanyName hoping to get a Server Error since it is Required. However, it saves the blank Company Name. Any ideas what might be missing?
This is MVC 2.0 but works just as well for 3.0
http://weblogs.asp.net/scottgu/archive/2010/01/15/asp-net-mvc-2-model-validation.aspx
Just look into Data Annotations, and do some Model Validation
EDIT:
your controller action will need something like
if(ModelState.IsValid)
{
//success logic
}
//failure logic
//return to view
you will also need
#Html.ErrorMessageFor(model => model.YourProperty)
in order to see the error messages being thrown.
Read the article it does a better job explaining this then anyone else will.
Just create a ViewModel object like this:
class Company
{
[Required]
[StringLength(40)]
[RegularExpression(#"someregexhere")]
public string CompanyName { get; set; }
}
And bind your View to that model. In this way you'll have both serverside and clientside validation. It's really easy.
#model Company
#using (Html.BeginForm()) {
Html.EditorFor(x => x.CompanyName)
<input type="submit" value="Save" />
}
Oh, this example uses Razor (MVC3), btw MVC2 works pretty much the same as far as I know.
Then just validate the incoming ViewModel in your controller by checking ModelState.IsValid.

Entity Framework & MVC Models

Our team is currently developing a web application, in that we have a class library with Entity Framework .edmx added and have generated the POCO classes.
Our Web Application is based on MVC, we have defined our classes in models with the same name and attributes (copy paste of the POCO classes from the .edmx). The .edmx class library is refrenced to MVC web application.
The Views are strongly typed of MVC Model classes. We have used MVC Models for Display, StringLength & Required.
In our controller when there is a CRUD operation we are accepting the POCO Classes Type such as
public ActionResult Create(EFModels.User user) { }
EFModels.User is a class from the .edmx (POCO generated class) and the MVC View is strongly typed to the model which is MvcWebApplication.Models.User.
Question is how are we getting data from the MvcWebApplication.Models.User (from Model) to EFModels.User (EF class) in the ActionResult Create ??
I am able to get the data, I know it is coz of the same property name. I tried changing the class name but still it works, but if we change the property name it does not work. I cannot understand the logic behind it.
Initially we never knew it didn`t work and we were using AutoMapper to convert the Model Class to Edmx POCO class.
Any ideas, Thanks.
The question is how are we getting the values of the Model Class to the EF class with any mapping. I don`t need to use AutoMapper, without using that I am getting the values.
Have a look at the code, hope that explains better...
//POCO CLASS
namespace EFModels
{
using System;
using System.Collections.Generic;
public partial class User
{
public int Id { get; set; }
public string Type { get; set; }
public string Name { get; set; }
}
}
//MVC Model Class
namespace MvcWebSamp.Models
{
public class User
{
public int Id { get; set; }
[Display(ResourceType = typeof(BasicTags), Name = "Type")]
[StringLength(15, ErrorMessageResourceName = "TypeLength", ErrorMessageResourceType = typeof(BasicTags))]
[Required(ErrorMessageResourceName = "TypeRequired", ErrorMessageResourceType = typeof(BasicTags))]
public string TypeName { get; set; }
public string Name { get; set; }
public Address Address { get; set; }
}
}
//MVC VIEW PAGE
<%# Page Title="" Language="C#" MasterPageFile="~/Views/Shared/Site.Master" Inherits="System.Web.Mvc.ViewPage<MvcWebSamp.Models.User>" %>
<asp:Content ID="Content1" ContentPlaceHolderID="TitleContent" runat="server">
User
</asp:Content>
<asp:Content ID="Content2" ContentPlaceHolderID="MainContent" runat="server">
<h2>User</h2>
<% using (Html.BeginForm("Create", "User", FormMethod.Post))
{
%>
<%: Html.ValidationSummary(true) %>
<fieldset>
<legend>User</legend>
<div class="editor-field">
<%: Html.LabelFor(model => model.TypeName) %>
<%: Html.EditorFor(model => model.TypeName)%>
<%: Html.ValidationMessageFor(model => model.TypeName)%>
</div>
<div class="editor-field">
<%: Html.LabelFor(model => model.Name) %>
<%: Html.EditorFor(model => model.Name)%>
<%: Html.EditorFor(model => model.Address.street)%>
</div>
<p>
<input type="submit" value="Create" />
</p>
</fieldset>
<% } %>
</asp:Content>
//Controller Method
public ActionResult Create(EFModels.User user)
{
Model1Container con = new Model1Container();
con.Users.Add(user);
con.SaveChanges();
return View("User");
}
When I hit the Create Button, I am posting data of the type MvcWebSamp.Models.User and in the Create Action I am able to get the data of the type EFModels.User user without using any AutoMapper. I want to know how this works???
You should be using your view model as the argument type for your create method:
[HttpPost]
public ActionResult Create(UserViewModel model)
{
if (ModelState.IsValid)
{
int id = UserService.CreateFromViewModel(model);
return RedirectToAction("View", new { id });
}
return View(model);
}
You controller should be designed to create and accept view models, and it passes those to an appropriate service which interacts with your data layer to create your domain model. This keeps your controller action quite thin.
You can use something like AutoMapper in your service to easily map between your view model and your domain model:
var user = Mapper.Map<UserViewModel, User>(model);
By giving DbContext to UI Layer you are creating dependancy between UI and database. Try to seperate it and use repository pattern and dependency injection.
Reference:
http://blogs.msdn.com/b/adonet/archive/2009/06/16/using-repository-and-unit-of-work-patterns-with-entity-framework-4-0.aspx
http://prodinner.codeplex.com/releases/view/66899
You aren't using your MvcWebSamp at all - as you can see, the controller takes the EFModel
public ActionResult Create(EFModels.User user)
It works because the properties are the same. You just need to modify the controller method signatures to take the MvcWebSamp objects instead, and then transform those objects to the EFModel objects.
Automapper should work. We use it all the time even with different property names. Please post usage of automapper that does not work for you. Otherwise see following post to make it work with different property names.
Usage of Automapper when property names are different
In order to use the Entity Framework, you need to create an Entity Data Model. For adding Entity Model:
1) Right click on Model Folder in the solution explorer.
2) Select Add a New Item.
for more details please check out the following link....
http://www.mindstick.com/Articles/6f3bb3c6-d195-487b-8b82-244bb417b249/?Model%20classes%20with%20Entity%20Framework%20in%20MVC
Thanks !!!

Categories