How to avoid view code duplication in MVC? - asp.net-mvc

I've made a very simple mockup of my problem.
If i have a complex person viewModel:
public class PersonViewModel
{
public string Name { get; set; }
public List<DogViewModel> Dogs { get; set; }
}
public class DogViewModel
{
public string Name { get; set; }
}
And i have 2 views : to edit person and create a person :
#model Custom.Models.PersonEditViewModel
#{
ViewBag.Title = "Edit";
}
<h2>Edit Person</h2>
<div class="PersonDataArea">
//this contain identical html in Create and Edit view
</div>
<div class="SelectDogAre">
//this contain identical html in Create and Edit view
</div>
<input type="button" value="save" />
#model Custom.Models.PersonCreateViewModel
#{
ViewBag.Title = "Create";
}
<h2>Create Person</h2>
<div class="PersonDataArea">
//this contain identical html in Create and Edit view
</div>
<div class="SelectDogAre">
//this contain identical html in Create and Edit view
</div>
<input type="button" value="save"/>
And of course 2 ViewModels for each view :
public class PersonEditViewModel
{
public PersonViewModel Person { get; set; }
}
public class PersonCreateViewModel
{
public PersonViewModel Person { get; set; }
}
Question :
as you can see i have 2 identical areas in Create and Edit views, and i want to avoid to suplicate code. I cant use Partial views because model in partial view will not be bound on post. How to avoid duplication of the view code??

You definitely need to use a Partial View.
First of all, you don't need two separate View Models. Just the PersonViewModel is enough.
Then you'll create a partial view _PersonEdit.cshtml (by the way you are missing the form in your code):
#model Custom.Models.PersonViewModel
#using(Html.BeginForm())
{
<div class="PersonDataArea">
//this contain identical html in Create and Edit view
</div>
<div class="SelectDogAre">
//this contain identical html in Create and Edit view
</div>
<input type="button" value="save"/>
}
And, two Views for Create and Edit:
Create:
#{
ViewBag.Title = "Create";
}
<h2>Create Person</h2>
#Html.Partial("_PersonEdit", Model)
Edit:
#{
ViewBag.Title = "Edit";
}
<h2>Edit Person</h2>
#Html.Partial("_PersonEdit", Model)
Alternatively, you can create only one view for both actions, and pass the ViewBag.Title from the controller to the view.

Related

Retrieving values from partial view during post method

I have a view which contains a dropdown list and on dropdownlist item being selected I load a partial view. And when the form is submitted I want to be able to get both the values from main view and partial view during form submit.
Here is the main view
#model AdminPortal.Areas.Hardware.Models.CreateModule
#{
ViewBag.Title = "Create Module";
Layout = "~/Views/shared/_BootstrapLayout.basic.cshtml";
}
#Html.ValidationSummary(true)
<fieldset class="form-horizontal">
<legend>Add a Module <small>Create</small></legend>
#using (Html.BeginForm("CreateModule", "Module", new{id="AddModuleForm"}))
{
#Html.ValidationSummary(true)
<div class ="controls">
<div class="input-block-level">#Html.TextBoxFor(model => model.ModuleId, new {#placeholder = "ModuleID"})</div>
<br/>
<div class ="input-block-level" id="selectedModuleTypeName">#Html.DropDownListFor(model => model.SelectedModuleTypeName, Model.TypeNames,"Select Moduletype", new{id = "ModuleList"})</div>
<br/>
<div id="partialDiv"></div>
</div>
<div class="form-actions" id="buttons">
<button type="submit" class="btn btn-primary" id="Submit">Save changes</button>
#Html.ActionLink("Cancel", "ModuleList", null, new { #class = "btn " })
</div>
}
</fieldset>
<div>
#Html.ActionLink("Back to List", "ModuleList")
</div>
<script>
$("#buttons").hide();
$("#ModuleList").on("change", function() {
var modId = $(this).val();
$.get('#Url.Action("GetModulePropertyName", "Module")', { moduleTypeValue: modId }, function(result) {
$("#partialDiv").html(result);
});
//uncomment following section to check if the partial view is working properly
/*.done(function() { alert("done"); })
.fail(function() { alert("fail"); })
.always(function() { alert("completed"); });*/
});
$("#buttons").show();
</script>
and here is the partial view
#model IEnumerable<string>
#foreach(var names in Model)
{
<div class="input-block-level">#Html.TextBoxFor(m=>names, new{Value="", placeholder=names})</div>
<br/>
}
Here is my model
public class CreateModule
{
//Empty form to handle form serialization
public CreateModule()
{
}
[Required]
public string ModuleId { get; set; }
[DataType(DataType.DateTime)]
public DateTime DateEntered { get; set; }
[Required]
public string SelectedModuleTypeName { get; set; }
public IEnumerable<SelectListItem> TypeNames { get; set; }
public List<Property> Properties { get; set; }
}
public class Property
{
public string Name { get; set; }
public string Value { get; set; }
}
Here is the method that script in main view forwards to
[HttpGet]
public ActionResult GetModulePropertyName(string moduleTypeValue)
{
var moduleKindId = _repository.GetModuleKindId(moduleTypeValue);
var modulePropertyNames = _repository.GetModuleKindPropertyNames(moduleTypeValue);
return PartialView("GetModulePropertyName",modulePropertyNames);
}
and finally here is httppost method for the main view
[HttpPost]
public ActionResult CreateModule(CreateModule moduleV)
{
var module = new Module
{
ModuleTypeId = Convert.ToInt64(moduleV.SelectedModuleTypeName),
ModuleId = moduleV.ModuleId,
DateEntered = moduleV.DateEntered,
};
if (ModelState.IsValid)
{
_repository.AddModule(module);
Success("Module added successfully!");
return RedirectToAction("ModuleList", "Module", new {area = "Hardware"});
}
Error("Something went wrong!");
return RedirectToAction("CreateModule", "Module", new { area = "Hardware" });
}
Current situation:
When the form is posted, the properties value of the model that is being passed via partial view is null. I get other values, like typename, Module ID.
What I'd want:
I also want to get the value of properties that is being passed via partial view.
You don't have any input field for the Properties property anywhere in your form. So it will always be null. That's normal.
Here's how you could proceed. Start by setting the correct navigational property so that the helper generates correct names of the corresponding input fields.
Also make sure that you are passing an IEnumerable<Property> model to the partial if you want to be able to get them back correctly:
[HttpGet]
public ActionResult GetModulePropertyName(string moduleTypeValue)
{
var moduleKindId = _repository.GetModuleKindId(moduleTypeValue);
IList<Property> model = ...
return PartialView("GetModulePropertyName", model.ToList());
}
and in your partial view use an editor template:
#model IList<Property>
#{
// This indicates the current navigational context to the helpers
ViewData.TemplateInfo.HtmlFieldPrefix = "Properties";
}
#Html.EditorForModel()
and the last step is to define a custom editor template for the Property class: ~/Views/Shared/EditorTemplates/Property.cshtml (note that the name and location of the template is important)
#model Property
<div class="input-block-level">
#Html.HiddenFor(m => m.Name)
#Html.TextBoxFor(m => m.Value, new { placeholder = Model.Name })
</div>
<br />
Try using the
List<Property>
as a model in your partial view and pass the CreateModule.Properties as model from your View
The problem is model binder can not figure out there
#Html.TextBoxFor(m=>names, new{Value="", placeholder=names})
belongs to as the "names" is not a property on your model class. If you need to bind to the CreateModule.Properties you need to change the partial view to emit textboxes with aproprate names, like this one:
#model IEnumerable<string>
#
{
int i=0;
}
#foreach(var names in Model)
{
<div class="input-block-level">#Html.TextBox("Properties[" + i + "].Value")</div>
<br/>
}

rename mvc model object in Razor View

I'm using MVC 4 and usually Visual Studio will create all the views for you. I have one form that just has one field and I want to just embed the create form into the Index View.
So the Index View has something like #model IEnumerable<Models.LinkModel>
So I access it by iterating through the Model collection.
But if I try to embed the form for the create action I need #model Models.LinkModel
and it is accessed by Model.Name as well. Is there a way to do this or use a different variable name?
Ok here is some extra info.
SO I have a model.
public class LinkModel
{
public string LinkUrl {get;set;}
}
I have a controller that has the Create and Index ActionResults.
Now in the Index view I have
#model IEnumerable<Models.LinkModel>
#{
ViewBag.Title = "Links";
}
I can do all my fancy logic to list all the links.
#foreach(link in Model)
{
<p>link.LinkUrl<p>
}
The Create View has this
#model Models.LinkModel // Note that it is just one item not IEnumerable
#{
ViewBag.Title = "Add Link";
}
#using (Html.BeginForm())
{
#Html.ValidationSummary(true)
<fieldset class="editor-fieldset">
<legend>LinkModel</legend>
<div class="editor-label">
#Html.LabelFor(model => model.LinkUrl)
</div>
<div class="editor-field">
#Html.TextBoxFor(model => model.LinkUrl)
</div>
<p>
<input type="submit" value="Add Link" />
</p>
</fieldset>
}
Now it seems pretty stupid to have a create form for just one field. I want to put this form on the Index page. Problem is that I access the object using the variable Model. I wanted to know if there is a way to have two seperate instances or be able to access the Model objects with different names.
Have a composite model with a list of items and 1 single item for the create
public class IndexModel {
public LinkModel CreateModel {get; set;}
public IEnumerable<LinkModel> Items {get; set;}
}
#model IndexModel
#using(Html.BeginForm("create")) {
#Html.EditorFor(m => m.CreateModel.Name);
}
#foreach(var item in Model.Items) {
#item.Name
}

Wiring partial view to model

I'm in a bit of a rut with MVC3 partial views. What I'm trying to do is have a partial view within one of my pages, and have that data from the partial view stored in the model that gets passed back into the main view. Though, what's happening right now is that after submitting the data, the model returned back to the controller has the partial view model set as null.
Model
public class MyModel {
...(other variables)...
[Required]
[NotMapped]
public virtual MyPartialView myPartial { get; set; }
}
View
#model MvcPartialViewExample.Models.MyModel
#{
ViewBag.Title = "Create";
}
#using (Html.BeginForm()) {
#Html.ValidationSummary(true)
<fieldset>
...(other data)...
#{
Html.RenderPartial("_MyPartialView", Model.MyPartialView, new ViewDataDictionary());
}
<p>
<input type="submit" value="Create" />
</p>
</fieldset>
}
<div>
#Html.ActionLink("Back to List", "Index")
</div>
Controller
public class MyModelController : Controller
{
private MyModelContext db = new MyModelContext();
//
// GET: /PartialViewTesting/Create
public ActionResult Create()
{
return View(new MyModel());
}
If you want to pass the Model.MyPartialView object back to the controller, then you need to add another Action method to the controller. Should look something like this:
[HttpPost]
public ActionResult Create(MyPartialView model)
{
// handle postback here
}

how to add some own forms in a ASP.NET MVC create view?

i have a problem, i had created a controller and a view for adding a new item from a specific model. the view looks like:
#modelModels.UserItem
#{
ViewBag.Title = "New";
}
<h2>New</h2>
<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>Device</legend>
<div class="editor-label">
#Html.LabelFor(model => model.name)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.name)
#Html.ValidationMessageFor(model => model.name)
</div>
<p>
<input type="submit" value="Create" />
</p>
</fieldset>
}
<div>
#Html.ActionLink("Back to List", "Index")
</div>
and the controller:
[HttpPost]
public ActionResult New(UserItem useritem)
{
if (ModelState.IsValid)
{
db.UserItems.AddObject(useritem);
db.SaveChanges();
return RedirectToAction("Index");
}
return View(useritems);
}
how i want to add a dropdown to the form in the view like this:
<select id="Select1">
<option>MARS</option>
</select>
how to access the data from the form after it was submitted in the controller?
Have view model for your page,this view model will be used in your view. So, only include fields from your model that you really need. In Get action you should create this view model and get the needed properties from your model and map them to your view model.
public class UserItemViewModel
{
/* Properties you want from your model */
public string Property1 { get; set; }
public string Property2 { get; set; }
public string Property3 { get; set; }
/* Property to keep selected item */
public string SelectedItem { get; set; }
/* Set of items to fill dropdown */
public IEnumerable<SelectListItem> SelectOptions { get; set; }
/* Fill the SelectListHere. This will be called from index controller */
public void FillOptions()
{
var items = new[] { "Mars", "Venus" }.
Select(x => new SelectListItem { Value = x, Text = x });
SelectOptions= new SelectList(items, "Value", "Text");
}
}
Change controller for receiving ViewModel instead of Model itself.
[HttpPost]
public ActionResult New(UserItemViewModel useritem)
{
/* Repopulate the dropdown, since the values are not posted with model. */
userItem.FillOptions();
if (ModelState.IsValid)
{
/* Create your actual model and add it to db */
// TODO: Map your properties from model to view model.
// Let's say you created a model with name userItemModel
db.UserItems.AddObject(userItemModel);
db.SaveChanges();
return RedirectToAction("Index");
}
return View(useritem);
}
You might need to change Index view controller little.(to fill dropdown)
[HttpGet]
public ActionResult Index()
{
/* Create new viewmodel fill the dropdown and pass it to view */
var viewModel = new UserItemViewModel();
viewModel.FillOptitons();
//TODO : From your model fill the required properties in view model as I mention.
return View(viewModel);
}
And your view,
/* Strongly typed view with viewmodel instead of model itself */
#modelModels.UserItemViewModel
/* This is the dropdown */
#Html.DropDownListFor(m => m.SelectedItem, Model.SelectOptions)
Add that property to your model
Use builtin EditorFor(preffered) or hand-written html to generate client-side input for that property.
Access submitted value by inspecting that property when user submits the form
I like emre's proposition of having a viewModel and I think is the Best solution to your question however just in case you don't want to go that way (you must have a really good reason because it is best) and still want a way to access the values of a form directly you can always use:
var x = Request["myFiledName"];
inside your controller to get to the values passed by your form.

ASP.Net MVC strongly typed partial views and inherited properties

I'm building a web-application with MS MVC 3 and have run into an issue, probably due to a hole in my understanding of model-binding.
Firstly, I have a fairly standard model (irrelevant stuff omitted for brevity, names changed to protect the privacy of innocent objects):
public class ModelBase
{
public int Id { get; set; }
}
public class Order : ModelBase
{
public List<Product> Products { get; set; }
}
public class Product : ModelBase
{
public int OrderId { get; set;}
}
For displaying and editing these, I have a View strongly typed to the Order class, containing a Partial View which is strongly typed to the Product class. The top of the partial view looks like this:
#model Product
#Html.ValidationSummary(true)
#Html.HiddenFor(model => model.Id)
#Html.HiddenFor(model => model.OrderId)
//loads of actual editable properties
I insert the partial into the main view like this:
#Html.Partial("EditorTemplates/Product", Model.Products.First())
...and when the view is rendered in the browser, the "Id" hidden input contains the Id of the Order, not the Id of the Product that I expect and need :(
What am I missing? Can it be fixed without changing the structure of the model and views?
On changing my test project I can confirm that
/Home/Index?id=33
or
/Home/Index/33
DOES overwrite the model value. Can you remove this parameter from the url?
You'd have to change the structure.
You could either put the partial content into the main view, or strongly type the partial to the Order not the Product, or construct the hidden field using Html.Hidden(). MVC is not putting the whole path to the item in the HiddenFor relative to the model in your post action.
ie. In the html you would see
<input type="hidden" name="Id" />
but you really want
<input type="hidden" name="Products[0].Id" />
in order to distinguish the fields and items.
The above problem still remains when you attempt to POST two different hidden inputs with the same name, however I also wrote a test project that works as far as the client.
Hope this helps:
Controller
public ActionResult Index()
{
Order order = new Order() { Id = 1 };
order.Products = new List<Product>() { new Product() { Id = 3, OrderId = 1 } };
return View("Order",order);
}
Models
public class Order : IdentityBase
{
public List<Product> Products { get; set; }
}
public class Product : IdentityBase
{
public int OrderId { get; set; }
}
public class IdentityBase
{
public int Id { get; set; }
}
View
#model MvcApplication1.Models.Order
#{
Layout = null;
}
<!DOCTYPE html>
<html>
<head>
<title>Order</title>
</head>
<body>
<div>
#Html.HiddenFor(model => model.Id)
#foreach (MvcApplication1.Models.Product product in Model.Products)
{
<div class="product">
#Html.Partial("Product", product)
</div>
}
</div>
</body>
</html>
Partial View
#model MvcApplication1.Models.Product
#Html.ValidationSummary(true)
#Html.HiddenFor(model => model.Id)
#Html.HiddenFor(model => model.OrderId)
Client Html
<!DOCTYPE html>
<html>
<head>
<title>Order</title>
</head>
<body>
<div>
<input id="Id" name="Id" type="hidden" value="1" />
<div class="product">
<input id="Id" name="Id" type="hidden" value="3" />
<input id="OrderId" name="OrderId" type="hidden" value="1" />
</div>
</div>
</body>

Resources