ASP.NET MVC3 Automapper Viewmodel/Model View validation - c#

(Again, an MVC validation question. I know, I know...)
I'd like to use AutoMapper (http://automapper.codeplex.com/) to validate fields in my Create Views for fields that are not in my database (and thus not in my DataModel).
Example: I have an Account/Create View for users to create a new account and I want both a Password and ConfirmPassword field so users have to enter their password twice for confirmation.
The Account table in the database looks like this:
Account[Id(PK), Name, Password, Email]
I've generated an ADO.NET Entity Data Model and from that, I generated the Models using an ADO.NET Self-Tracking Entity Generator.
I've also written a custom AccountViewModel for validation annotations like [Required].
So, to summarize, this is my project structure:
Controllers:
AccountController
Models:
Database.edmx (auto-generated from database)
Model.Context.tt (auto-generated from edmx)
Model.tt (auto-generated from edmx)
AccountViewModel.cs
Views:
Account
Create.cshtml
The code of my AccountViewModel looks like this:
public class AccountViewModel
{
public int Id { get; set; }
[Required]
public string Name { get; set; }
[Required]
public string Password { get; set; }
[Required]
[Compare("Password")]
public string ConfirmPassword { get; set; }
}
Now, my Create View looks like this:
#model AutoMapperTest.Models.Account
<script src="#Url.Content("~/Scripts/jquery-1.4.4.min.js")" type="text/javascript"></script>
<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>Account</legend>
<div class="editor-label">
Name
</div>
<div class="editor-field">
#Html.TextBox("Name")
#Html.ValidationMessageFor(model => model.Name)
</div>
<div class="editor-label">
Email
</div>
<div class="editor-field">
#Html.TextBox("Email")
#Html.ValidationMessageFor(model => model.Email)
</div>
<div class="editor-label">
Password
</div>
<div class="editor-field">
#Html.TextBox("Password")
#Html.ValidationMessageFor(model => model.Password)
</div>
<div class="editor-label">
Confirm your password
</div>
<div class="editor-field">
#Html.TextBox("ConfirmPassword");
#Html.ValidationMessageFor(model => model.ConfirmPassword)
</div>
<p>
<input type="submit" value="Create" />
</p>
</fieldset>
}
<div>
#Html.ActionLink("Back to List", "Index")
</div>
My code fails because my Model does not contain the ConfirmPassword field of course.
Now, a little bird whispered to me the AutoMapper could fix that for me. But I can't figure it out... Can someone please tell me what I have to do to make this work? My AccountController looks like this now:
private readonly AccountViewModel _viewModel = new AccountViewModel();
private readonly DatabaseEntities _database = new DatabaseEntities();
//
// GET: /Account/Create
public ActionResult Create()
{
Mapper.CreateMap<AccountViewModel, Account>();
return View("Create", _viewModel);
}
//
// POST: /Account/Create
[HttpPost]
public ActionResult Create(AccountViewModel accountToCreate)
{
try
{
if (ModelState.IsValid)
{
var newAccount = new Account();
Mapper.Map(accountToCreate, newAccount);
_database.Account.AddObject(newAccount);
_database.SaveChanges();
}
return RedirectToAction("Index");
}
catch
{
return View();
}
}
But this doesn't work... (Got the example from http://weblogs.asp.net/shijuvarghese/archive/2010/02/01/view-model-pattern-and-automapper-in-asp-net-mvc-applications.aspx)
Can anyone please enlighten me on this matter? Thank you very much, and my apologies for the wall of text and the hundreds of questions about the same subject...

Few remarks about your code:
Your view is strongly typed (#model declaration) to the Account model whereas it should be typed to the AccountViewModel view model (there is no point in declaring a view model if you don't use it in the view).
AutoMapper is not used for validation, only for converting between types
You don't need to declare a readonly field for your view model (AccountViewModel) inside the controller. You could instantiate the view model inside the GET action and leave the default model binder instantiate it as action argument for the POST action.
You should do the AutoMapper configuration (Mapper.CreateMap<TSource, TDest>) only once for the entire application (ideally in Application_Start) and not inside a controller action
There is no Email field on your view model which might be the reason for the update to fail (especially if this field is required in your database)
So here's how your code might look like:
public ActionResult Create()
{
var model = new AccountViewModel();
return View("Create", model);
}
[HttpPost]
public ActionResult Create(AccountViewModel accountToCreate)
{
try
{
if (ModelState.IsValid)
{
var newAccount = Mapper.Map<AccountViewModel, Account>(accountToCreate);
_database.Account.AddObject(newAccount);
_database.SaveChanges();
}
return RedirectToAction("Index");
}
catch
{
return View();
}
}

replace the first line of you view with
#model AutoMapperTest.AccountViewModel
Also you only need to call Mapper.CreateMap once for app lifetime (e.g. at app start)

Related

How can I pass hidden field value from view to controller ASP.NET MVC 5?

I am trying to pass hidden field value from view to controller by doing the following
#Html.HiddenFor(model => model.Articles.ArticleId)
and also tried
<input type="hidden" id="ArticleId" name="ArticleId" value="#Model.Articles.ArticleId" />
On both instances the value of ArticleId is 0 but when i use TextboxFor i can see the correct ArticleId, please help
Here it is
View
#model ArticlesCommentsViewModel
....
#using (Html.BeginForm("Create", "Comments", FormMethod.Post))
{
<div class="row">
<div class="col-xs-10 col-md-10 col-sm-10">
<div class="form-group">
#Html.LabelFor(model => model.Comments.Comment, new { #class = "control-label" })
#Html.TextAreaFor(m => m.Comments.Comment, new { #class = "ckeditor" })
#Html.ValidationMessageFor(m => m.Comments.Comment, null, new { #class = "text-danger"})
</div>
</div>
</div>
<div class="row">
#*#Html.HiddenFor(model => model.Articles.ArticleId)*#
<input type="hidden" id="ArticleId" name="ArticleId" value="#Model.Articles.ArticleId" />
</div>
<div class="row">
<div class="col-xs-4 col-md-4 col-sm-4">
<div class="form-group">
<input type="submit" value="Post Comment" class="btn btn-primary" />
</div>
</div>
</div>
}
Controller
// POST: Comments/Create
[HttpPost]
public ActionResult Create(CommentsViewModel comments)//, int ArticleId)
{
var comment = new Comments
{
Comment = Server.HtmlEncode(comments.Comment),
ArticleId = comments.ArticleId,
CommentByUserId = User.Identity.GetUserId()
};
}
Model
public class CommentsViewModel
{
[Required(ErrorMessage = "Comment is required")]
[DataType(DataType.MultilineText)]
[Display(Name = "Comment")]
[AllowHtml]
public string Comment { get; set; }
public int ArticleId { get; set; }
}
ViewModel
public class ArticlesCommentsViewModel
{
public Articles Articles { get; set; }
public CommentsViewModel Comments { get; set; }
}
The model in the view is ArticlesCommentsViewModel so therefore the parameter in your POST method must match. Your use of
#Html.HiddenFor(model => model.Articles.ArticleId)
is correct, but you need to change the method to
[HttpPost]
public ActionResult Create(ArticlesCommentsViewModel model)
and the model will be correctly bound.
As a side note, your ArticlesCommentsViewModel should not contain data models, and instead should contain only those properties you need in the view. If typeof Articles contains properties with validation attributes, ModelState would be invalid because your not posting all properties of Article.
However, since CommentsViewModel already contains a property for ArticleId, then you could just use
#Html.HiddenFor(model => model.Comments.ArticleId)
and in the POST method
[HttpPost]
public ActionResult Create([Bind(Prefix="Comments")]CommentsViewModel model)
to effectively strip the "Comments" prefix
In your controller, you need to pass the hidden value with the model,
for example, if you have a userId as a hidden value, in your Page you add:
#Html.HiddenFor(x => x.UserId)
In your model of course you would already have UserId as well.
In your controller, you need the model as a parameter.
public async Task<ActionResult> ControllerMethod(YourViewmodel model) { model.UserId //this should be your HiddenValue
I guess your model have another class called Articles inside CommentsViewModel.Change your controller function for accessing the ArticleId accordingly.
[HttpPost]
public ActionResult Create(CommentsViewModel comments)//, int ArticleId)
{
var comment = new Comments
{
Comment = Server.HtmlEncode(comments.Comment),
ArticleId = comments.Articles.ArticleId, // Since you are using model.Articles.ArticleId in view
CommentByUserId = User.Identity.GetUserId()
};
}
In my case, I didn't put the hidden input in the form section, but out of form, so it's not send to backend. Make sure put hidden input inside the form.
Also make sure name attribute is specified on the hidden field. Element's "id" is often used on client side but "name" on server side.
<input type="hidden" value="#ViewBag.selectedTraining" id="selectedTraining"
name="selectedTraining" />
In my case, I was passing a couple of fields back and forth between controllers and views. So I made use of hidden fields in the views.
Here's part of the view. Note a controller had set "selectedTraining" and "selectedTrainingType" in the ViewBag to pass to the view. So I want these values available to pass on to a controller. On the hidden tag, the critical thing is set to the "name" attribute. "id" won't do it for you.
#using (Html.BeginForm("Index", "ComplianceDashboard"))
{
<input type="hidden" value="#ViewBag.selectedTraining" id="selectedTraining" name="selectedTraining" />
<input type="hidden" value="#ViewBag.selectedTrainingType" id="selectedTrainingType" name="selectedTrainingType" />
if (System.Web.HttpContext.Current.Session["Dashboard"] != null)
{
// Show Export to Excel button only if there are search results
<input type="submit" id="toexcel" name="btnExcel" value="Export To Excel" class="fright" />
}
<div id="mainDiv" class="table">
#Html.Grid(Model).Columns(columns =>
Then back on the controller:
// POST: Dashboard (Index)
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Index(string excel)
{
string selectedTraining, selectedTrainingType;
selectedTraining = Request["selectedTraining"];
selectedTrainingType = Request["selectedTrainingType"];
Or can put the requests as parameters to the method: public ActionResult Index(string excel, string selectedTraining, string selectedTrainingType)

Edit and update rows in dynamic table in Asp.net mvc4 razor view

I am new to asp.net MVC. I have a dynamic table in my project. Adding dynamic rows in table is achieved with the help of following link
Adding and deleting rows in dynamic table in Asp.net mvc razor view
I need to edit and update the dynamic table.
I have tried following code
My sample model
public class Gift
{
public string Name { get; set; }
public double Price { get; set; }
}
public class GiftViewModel
{
public string Age { get; set; }
public DateTime TheDate { get; set; }
public IEnumerable<Gift> Gifts { get; set; }
}
My sample Controller
public class HomeController : Controller
{
[HttpGet]
public ActionResult Index()
{
return View();
}
[HttpPost]
public ActionResult Index(GiftViewModel model)
{
// do work here
return RedirectToAction("Index");
}
public ViewResult AddNew()
{
return View("_TimeSheetView");
}
}
My sample Partial View
#model HelloWorldMvcApp.Gift
#using (Html.BeginCollectionItem("giftList"))
{
<div>
<span class="drop_medium">
#Html.TextBoxFor(m => m.Name)
</span>
<span class = "drop_medium">
#Html.TextBoxFor(m => m.Price)
</span>
</div>
}
My sample main view
#model HelloWorldMvcApp.GiftViewModel
#using (Html.BeginForm())
{
#Html.TextBoxFor(m => m.Age)
#foreach (var data in Model.Gifts)
{
{ Html.RenderPartial("_TimeSheetView", data); }
}
#Html.ActionLink("Add another", "AddNew", null, new { id="addItem" })
<input type="submit" value="Save"/>
}
<script type="text/javascript">
$("#addItem").click(function () {
$.ajax({
url: this.href,
cache: false,
success: function (html) { $("#dynamic").append(html); }
});
return false;
});
</script>
When I click 'Add Another' button a row is added to the table. After editing the values in the table When I click submit button I receive nothing in the controller. The IEnumerable Gifts variable is null. How to take the table values to the controller. Please help me to fix this is issue. Thanks in advance
Your model's collection property is named Gifts so the partial needs to be
#model HelloWorldMvcApp.Gift
#using (Html.BeginCollectionItem("Gifts")) // not "giftlist"
{
...
}
This will generate inputs with the correct name attributes for binding to a collection (where ## is a Guid)
<input name="Gifts[##].Name" ... />
<input name="Gifts[##].Price" ... />
<input type="hidden" name="Gifts.Index" value="##" />
The problem you're facing is the name of the rendered input isnt matching your model structure. There are a couple of ways out of this:
Make an editor template for the model type
your partial view:
#model IEnumerable<HelloWorldMvcApp.Gift>
#Html.EditorForModel("","Gifts")
and an EditorTemplate for the Gift model:
#model HelloWorldMvcApp.Gift
<div>
<span class="drop_medium">
#Html.TextBoxFor(m => m.Name)
</span>
<span class = "drop_medium">
#Html.TextBoxFor(m => m.Price)
</span>
</div>
Manually create the inputs with the properly parsed name - "Gifts[x].Property"
Obviously the first option is far cleaner and imho preferred.
Hope this works, and helps :)

MVC Model child object null on HTTP post

Hope someone can help me. I am new to MVC, coming from a winforms/console/vb6background.
Apologies if this has already been answered, I am stuggling to understand how I can resolve the below issue.
I have a view model :
public class testvm
{
public int id { get; set; }
public DateTime date { get; set; }
public student studentID { get; set; }
public testvm() { }
public testvm (student s)
{
studentID = s;
}
}
I am pre-populating the student child object of this ViewModel before it is passed to the view.
Student Model :
public class student
{
[Key]
public int ID { get; set; }
public string Name { get; set; }
}
The problem I have is when the model is returned to the create HTTP post method the student child object is blank.
The controller code :
// GET: testvms/Create
public ActionResult Create(int sId)
{
student a = db.students.Find(sId);
testvm b = new testvm(a);
return View(b);
}
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create([Bind(Include = "id,date,student")] testvm testvm)
{
if (ModelState.IsValid)
{
db.testvws.Add(testvm);
db.SaveChanges();
return RedirectToAction("Index");
}
return View(testvm);
}
View code:
#model WebApplication2.Models.testvm
#{
ViewBag.Title = "Create";
}
<h2>Create</h2>
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
<div class="form-horizontal">
<h4>testvm</h4>
<hr />
#Html.HiddenFor(model => model.studentID.ID)
#Html.ValidationSummary(true, "", new { #class = "text-danger" })
<div class="form-group">
#Html.LabelFor(model => model.date, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.date, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.date, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="Create" class="btn btn-default" />
</div>
</div>
</div>
}
<div>
#Html.ActionLink("Back to List", "Index")
</div>
#section Scripts {
#Scripts.Render("~/bundles/jqueryval")
}
The model object on the view is populated with the student information. When this is passed back to Create POST controller the student child object is null!
Can somebody please advise where I am going wrong or of the correct way to achieve this?
My application will have many forms that will all need to be pre-populated with student information. Each student will have many forms that will need to be filled out for them.
Many thanks in advance,
Rob
For every property in domain model (in your case testvm) you must have an EditorFor or Input element (like TextBoxFor or so) on your view(or HiddenFor for ID or other non user ui data).It may be a pain binding nested models in MVC as the DefaultModelBinder may not be able to bind whole object.However it would be safer approach to expose only the required properties on view like
#Html.HiddenFor(model => model.studentID.ID)
#Html.HiddenFor(model => model.studentID.Name)
and later on Controller Side
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create(testvm testvm)
{
var originalobj=db.get //get fresh copy from data store
originalobj.Name=testvm.Name;
// ...other properties
//perform required operations on originalobj
}
you may use AutoMapper for this Purpose as
Mapper.CreateMap<testvm,testvm>();
originalobj=Mapper.Map<testvm,testvm>(testvm,originalobj);
you may find more information about Automapper on :
https://github.com/AutoMapper/AutoMapper/wiki/Getting-started
Your property name is called studentId (even though standard C# property naming convention dictates that it should have been called StudentId):
public student studentID { get; set; }
But in your Bind attribute you seem to have specified some student property which doesn't really exist on your view model:
[Bind(Include = "id,date,student")]
So you probably want to get rid of this Bind attribute from your controller action:
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create(testvm testvm)
{
...
}
Also note that you only have a hidden field for the student id inside your form:
#Html.HiddenFor(model => model.studentID.ID)
You don't have a corresponding hidden field for the student name property, so it will never be posted back to your controller action.
Your attribute [Bind(Include = "id,date,student")] should include the names of the properties that you want to be set, student isn't in your model, but studentID is, they have to match.
You don't have to explicitly specify all of the field names that you want to be bound to your model, by default they will be bound anyway unless you tell the binder NOT to bind it by using [Bind(Exclude = "id,date,student")]. Therefore as it currently stands, I'd recommend removing your Include attribute to ease maintenance unless there is an important reason for using it and simply ensure that the models that you bind to only include the values you need.
Secondly, you have to make sure that any values that you are posting back from a form in your view have the same parameter names and are structured the same as the ones that you want to be bound to the request model.
This:
#Html.HiddenFor(model => model.studentID.ID)
Is not the same as:
#Html.HiddenFor(model => model.studentID)

Not able to see textbox in asp.net mvc page

I am entirely new to asp.net mvc and this is my first sample project that I need to show one textbox in one view when the user entered a value in that textbox, I need to display that value in label in another view.
for that I have done like this ..
this is my controller class
public class TextBoxController : Controller
{
//
// GET: /TextBox/
public ActionResult Index()
{
return View();
}
}
and this is my model class
namespace MvcTestApplication.Models
{
public class TextboxModel
{
[Required]
[Display(Name= "Textbox1")]
public string EnteredValue { get; set; }
}
}
and this is my view
#model MvcTestApplication.Models.TextboxModel
#{
ViewBag.Title = "TextboxView";
}
<h2>TextboxView</h2>
#using (Html.BeginForm())
{
<div>
<fieldset>
<legend>Enter Textbox Value</legend>
<div class ="editor-label">
#Html.LabelFor(m => m.EnteredValue)
</div>
<div class="editor-field">
#Html.TextBoxFor(m=>m.EnteredValue)
</div>
<p>
<input type="submit" value="Submit Value" />
</p>
</fieldset>
</div>
}
I am not able to see any textbox and any button on page and I am getting error like
HTTP:404 : Resource Cannot be found
I am using visual studio 2012 and mvc4..
would any pls suggest any idea on this one ..
Many thanks..
RE-WRITTEN
In simple terms, to access a page on ASP.NET MVC, you should point the URL to its controller name. In this case, TextBox:
localhost:2234/TextBox/TextBox
Also, you forgot to add an ActionResult for this new view. When you're loading the page, it's going through the Index one, which is empty.
The final code should look like this:
Controller
public class TextBoxController : Controller
{
public ActionResult Index()
{
return View();
}
public ActionResult TextBox(MvcApplication1.Models.TextBoxModel model)
{
return View(model);
}
}
Model
public class TextBoxModel
{
[Required]
[Display(Name = "Textbox1")]
public string EnteredValue { get; set; }
}
Razor View (Index)
#{
ViewBag.Title = "Index";
}
<h2>Index</h2>
Razor View (TextBox)
#model MvcApplication1.Models.TextBoxModel
#{
ViewBag.Title = "TextBox";
}
<h2>TextBox</h2>
#using (Html.BeginForm())
{
<div>
<fieldset>
<legend>Enter Textbox Value</legend>
<div class ="editor-label">
#Html.LabelFor(m => m.EnteredValue)
</div>
<div class="editor-field">
#Html.TextBoxFor(m=>m.EnteredValue)
</div>
<p>
<input type="submit" value="Submit Value" />
</p>
</fieldset>
</div>
}
Make sure that you have registered the URL via route config.
Find more about asp.net routing here
UPDATE:
Make sure that file name of your view is Index.cshtml since your controller doesn't specify any return view names.

Why is my model not passed as parameter with form post

I'm having trouble understanding why my model is not passed along with its values to my controller when posting a form.
I have a view with a strongly typed model (UnitContract) that is being fetched from a webservice, that holds a set of values. In my action I'm trying to fetch int ID and bool Disabled fields that exists in my model. When debugging, I see that my model being passed from the form doesn't contain any values at all. What am I missing?
My view (UnitContract as strongly typed model):
...
<form class="pull-right" action="~/UnitDetails/EnableDisableUnit" method="POST">
<input type="submit" class="k-button" value="Enable Unit"/>
</form>
My controller action:
[HttpPost]
public ActionResult EnableDisableUnit(UnitContract model)
{
var client = new UnitServiceClient();
if (model.Disabled)
{
client.EnableUnit(model.Id);
}
else
{
client.DisableUnit(model.Id);
}
return RedirectToAction("Index", model.Id);
}
Sounds like you need to add the fields from your model to your form. Assuming your view accepts a UnitContract model, then something like this should work:
<form class="pull-right" action="~/UnitDetails/EnableDisableUnit" method="POST">
#Html.HiddenFor(x => x.Id)
#Html.HiddenFor(x => x.Disabled)
<input type="submit" class="k-button" value="Enable Unit"/>
</form>
Now when you submit the form, it should submit the fields to your model.
The MVC framework will use the data from the form to create the model. As your form is essentially empty, there is no data to create the model from, so you get an object without any data populated.
The only data that is sent from the browser in the request when you post the form, is the data that is inside the form. You have to put the data for the properties in the model as fields in the form, so that there is something to populate the model with.
Look into using #Html.HiddenFor(). Put these in your form, and the data you want to see posted back to your controller should be there. For example, your form would look something like...
<form class="pull-right" action="~/UnitDetails/EnableDisableUnit" method="POST">
#Html.HiddenFor(x => x.Id)
#Html.HiddenFor(x => x.IsDisabled)
<input type="submit" class="k-button" value="Enable Unit"/>
</form>
Let's say you have a model like this:
public class UnitContract
{
public int Id { get; set; }
public DateTime SignedOn { get; set; }
public string UnitName { get; set; }
}
Your view would look something like this:
#using (Html.BeginForm()) {
#Html.AntiForgeryToken()
#Html.ValidationSummary(true)
<fieldset>
<legend>UnitContract</legend>
#Html.HiddenFor(model => model.Id)
<div class="editor-label">
#Html.LabelFor(model => model.SignedOn)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.SignedOn)
#Html.ValidationMessageFor(model => model.SignedOn)
</div>
<div class="editor-label">
#Html.LabelFor(model => model.UnitName)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.UnitName)
#Html.ValidationMessageFor(model => model.UnitName)
</div>
<p>
<input type="submit" value="Save" />
</p>
</fieldset>
}
In your controller:
[ValidateAntiForgeryToken]
[HttpPost]
public ActionResult Edit(UnitContract unitContract)
{
// do your business here .... unitContract.Id has a value at this point
return View();
}
Hope this is helpful.

Categories

Resources