I am calling my partial view like this:
<% Html.RenderPartial("~/controls/users.ascx"); %>
Can I pass parameters to partial view? How will I access them in the actual users.ascx page?
You could pass a model object to the partial (for example a list of strings):
<% Html.RenderPartial("~/controls/users.ascx", new string[] { "foo", "bar" }); %>
Then you strongly type the partial and the Model property will be of the appropriate type:
<%# Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<System.Collections.Generic.IEnumerable<string>>" %>
<% foreach (var item in Model) { %>
<div><%= Html.Encode(item) %></div>
<% } %>
There is another overload for RenderPartial that will pass your model through.
<% Html.RenderPartial("~/controls/users.ascx", modelGoesHere); %>
How to access? Just like you normally would with any view:
<%= Model.MagicSauce %>
It took a while to sink in, but MVC means you use a Model, a View, and a Controller one way or another for just about everything, including Partial Views. How all three elements fit together can be a little intimidating at first. I'd never done one until just now, and it works --Woohoo!
Hope this helps the next person.... Sorry, I'm using razor instead of .Net forms. I'm also pulling data from a SQL Server database into Entity Framework, which a developer is likely to use. I also probably went a little overboard with WebGrid, which is so much more elegant than a foreach statement. A basic #webgrid.GetHtml() will display every column and row.
Background
In this working example, users have uploaded pictures. Their pictures are displayed in their edit form using a partial view. The ImageID and FileName metadata is persisted in SQL Server while the file itself is persisted in the ~/Content/UserPictures directory.
I know it's kinda half vast, because all the details of uploading and editing personal data isn't shown. Just the germane parts of using a Partial View are focused on, albeit with some bonus EF thrown in. The namespace is MVCApp3 for S&G.
Partial View Model ViewModels.cs
The SQL Server Images table includes many more columns in addition to ImageID and FileName such as [Caption], [Description], a MD5 hash to prevent the same image being uploaded multiple times, and upload date. The ViewModel distills the Entity down to the bare minimum needed for a user to see their pictures.
public class Picts
{
public int ImageID { get; set; }
public string FileName { get; set; }
}
Main View View Edit.cshtml
Note the cast/convert to strongly type the ViewData[].
#Html.Partial(
partialViewName: "Picts",
model: (IEnumerable<MVCApp3.Models.Picts>)ViewData["Picts"]
)
If you don't set the strongly-typed model to use for the Partial View you'll get a "The model item passed into the dictionary is of type 'System.Data.Entity.DynamicProxies..." error because it assumes you're passing the parent/master model.
Partial View View Picts.cshtml (the whole file contents is shown)
#model IEnumerable<MVCApp3.Models.Picts>
#{
var pictsgrid = new WebGrid(Model);
}
#pictsgrid.GetHtml(
tableStyle: "grid",
displayHeader: false,
alternatingRowStyle: "alt",
columns: pictsgrid.Columns(
pictsgrid.Column(format:#<text><img src="#Url.Content("~/Content/Users/" + #item.FileName)" alt="#item.ImageID" width="200" />
#Html.ActionLink(linkText: "Delete", actionName: "DeletePicture", routeValues: new { id = #item.ImageID })
</text>)
))
Controller IdentityController.cs
Set the data content into the ViewData["MyPartialViewModelKeyName"] your partial view will consume. You can give the dictionary key any name you want, but I gave it ViewData["Picts"] to be consistent with the partial view file name and its view model class definition.
Because the pictures may be shared amongst multiple users there is a many-to-many table with a corresponding PITA query in Entity Framework using nested froms and inner joins to return just the pictures belonging to, or shared with, a user:
public class IdentityController : Controller
{
private EzPL8Entities db = new EzPL8Entities();
// GET: /Identity/Edit/5
[Authorize]
public ActionResult Edit(int? id)
{
if (id == null)
return new HttpNotFoundResult("This doesn't exist");
// get main form data
ezpl8_UsersPhysicalIdentity ezIDobj = db.ezpl8_UsersPhysicalIdentity.Find(id)
// http://learnentityframework.com/LearnEntityFramework/tutorials/many-to-many-relationships-in-the-entity-data-model/
// get partial form data for just this user's pictures
ViewData["Picts"] = (from user in db.ezpl8_Users
from ui in user.ezpl8_Images
join image in db.ezpl8_Images
on ui.ImageID equals image.ImageID
where user.ezpl8_UserID == id
select new Picts
{
FileName = image.FileName,
ImageID = image.ImageID
}
).ToList();
return View(ezIDobj);
}
// Here's the Partial View Controller --not much to it!
public ViewResult Picts(int id)
{
return View(ViewData["Picts"]);
}
[Authorize] //you have to at least be logged on
public ActionResult DeletePicture(int id)
{
//ToDo: better security so a user can't delete another user's picture
// TempData["ezpl8_UserID"]
ezpl8_Images i = db.ezpl8_Images.Find(id);
if (i != null)
{
var path = System.IO.Path.Combine(Server.MapPath("~/Content/Users"), i.FileName);
System.IO.File.Delete(path: path);
db.ezpl8_Images.Remove(i);
db.SaveChanges();
}
return Redirect(Request.UrlReferrer.ToString());
}
protected override void Dispose(bool disposing)
{
db.Dispose();
base.Dispose(disposing);
}
}
// get main form data
ezpl8_UsersPhysicalIdentity ezIDobj = db.ezpl8_UsersPhysicalIdentity.Find(id)
// http://learnentityframework.com/LearnEntityFramework/tutorials/many-to-many-relationships-in-the-entity-data-model/
// get partial form data for just this user's pictures
ViewData["Picts"] = (from user in db.ezpl8_Users
from ui in user.ezpl8_Images
join image in db.ezpl8_Images
on ui.ImageID equals image.ImageID
where user.ezpl8_UserID == id
select new Picts
{
FileName = image.FileName,
ImageID = image.ImageID
}
).ToList();
return View(ezIDobj);
}
// Here's the Partial View Controller --not much to it!
public ViewResult Picts(int id)
{
return View(ViewData["Picts"]);
}
[Authorize] //you have to at least be logged on
public ActionResult DeletePicture(int id)
{
//ToDo: better security so a user can't delete another user's picture
// TempData["ezpl8_UserID"]
ezpl8_Images i = db.ezpl8_Images.Find(id);
if (i != null)
{
var path = System.IO.Path.Combine(Server.MapPath("~/Content/Users"), i.FileName);
System.IO.File.Delete(path: path);
db.ezpl8_Images.Remove(i);
db.SaveChanges();
}
return Redirect(Request.UrlReferrer.ToString());
}
protected override void Dispose(bool disposing)
{
db.Dispose();
base.Dispose(disposing);
}
}
Related
I followed Darin's post at
multi-step registration process issues in asp.net mvc (splitted viewmodels, single model)
Its a very elegant solution, however im having trouble seeing how you would populate the individual step viewmodels with data. Im trying to emulate amazons checkout step-system which starts with selecting an address, then shipping options, then payment information.
For my first viewmodel i require a list of addresses for my current logged in user which i poll the database for to display on screen
In my head, this is the viewmodel that makes sense to me.
[Serializable]
public class ShippingAddressViewModel : IStepViewModel
{
public List<AddressViewModel> Addresses { get; set; }
[Required(ErrorMessage="You must select a shipping address")]
public Int32? SelectedAddressId { get; set; }
#region IStepViewModel Members
private const Int32 stepNumber = 1;
public int GetStepNumber()
{
return stepNumber;
}
#endregion
}
However there seems to be no good way to populate the addresses from the controller.
public class WizardController : Controller
{
public ActionResult Index()
{
var wizard = new WizardViewModel();
wizard.Initialize();
return View(wizard);
}
[HttpPost]
public ActionResult Index(
[Deserialize] WizardViewModel wizard,
IStepViewModel step)
{
wizard.Steps[wizard.CurrentStepIndex] = step;
if (ModelState.IsValid)
{
if (!string.IsNullOrEmpty(Request["next"]))
{
wizard.CurrentStepIndex++;
}
else if (!string.IsNullOrEmpty(Request["prev"]))
{
wizard.CurrentStepIndex--;
}
else
{
// TODO: we have finished: all the step partial
// view models have passed validation => map them
// back to the domain model and do some processing with
// the results
return Content("thanks for filling this form", "text/plain");
}
}
else if (!string.IsNullOrEmpty(Request["prev"]))
{
// Even if validation failed we allow the user to
// navigate to previous steps
wizard.CurrentStepIndex--;
}
return View(wizard);
}
}
So i removed the list of address view models
[Serializable]
public class ShippingAddressViewModel : IStepViewModel
{
[Required(ErrorMessage="You must select a shipping address")]
public Int32? SelectedAddressId { get; set; }
#region IStepViewModel Members
private const Int32 stepNumber = 1;
public int GetStepNumber()
{
return stepNumber;
}
#endregion
}
This is what i came up with a custom editor template for the view model. It calls a Html.RenderAction which returns a partial view from my user controller of all the addresses and uses Jquery to populate a hidden input field for the view model's required SelectedAddressId property.
#model ViewModels.Checkout.ShippingAddressViewModel
<script src="../../Scripts/jquery-1.7.1.js" type="text/javascript"></script>
<script type="text/javascript">
$(document).ready(function () {
//Check to see if the shipping id is already set
var shippingID = $("#SelectedAddressId").val();
if (shippingID != null) {
$("#address-id-" + shippingID.toString()).addClass("selected");
}
$(".address-id-link").click(function () {
var shipAddrId = $(this).attr("data-addressid").valueOf();
$("#SelectedAddressId").val(shipAddrId);
$(this).parent("", $("li")).addClass("selected").siblings().removeClass("selected");
});
});
</script>
<div>
#Html.ValidationMessageFor(m => m.SelectedAddressId)
#Html.HiddenFor(s => s.SelectedAddressId)
<div id="ship-to-container">
#{Html.RenderAction("UserAddresses", "User", null);}
</div>
</div>
And the users controller action
[ChildActionOnly]
public ActionResult UserAddresses()
{
var user = db.Users.Include("Addresses").FirstOrDefault(
u => u.UserID == WebSecurity.CurrentUserId);
if (user != null)
{
return PartialView("UserAddressesPartial",
Mapper.Map<List<AddressViewModel>>(user.Addresses));
}
return Content("An error occured");
}
The partial view
#model IEnumerable<AddressViewModel>
<ul>
#foreach (var item in Model)
{
<li id="address-id-#item.AddressID">
#Html.DisplayFor(c => item)
<a class="address-id-link" href="#" data-addressid="#item.AddressID">Ship To this Address
</a></li>
}
</ul>
My solution just seems super out of the way/sloppy to me, is there a better more concise way to populate the viewmodel than using partial views from a different controller for this?
There's nothing wrong with using a child action like this to populate the user's addresses. In fact, I think this is actually the optimal approach. You've got full separation of concerns and single responsibility in play. Just because something requires more "pieces" (extra action, views, etc.) doesn't make it sloppy or otherwise wrong.
The only other way to handle this would be with dependency injection. Namely, your ShippingAddressViewModel would need a dependency of the currently logged in user, so that it could populate the list of addresses from that in its constructor. However, since ShippingAddressViewModel is not exposed in your view, you would have to pass the dependency through Wizard which is a bit of code smell. Wizard is not dependent on a user, but it would have dependence forced upon it by virtue of having your view model abstracted away inside it.
Long and short, while there's a way you could do this without the child actions and partial views, it would actually be nastier and sloppier than with them.
I am currently have a bunch of users sitting in a Membership Database and I want to be able to create a page in an MVC App that will be designed to pull back all the user information and display it on the page, I have created the solution below however I want be able to show the data in a Grid View instead of a list:
Controller:
public ActionResult Index()
{
var systemUsers = Membership.GetAllUsers();
return View(systemUsers);
}
View:
<ul>
<%foreach (MembershipUser user in Model){ %>
<li><%=user.UserName %></li>
<% }%>
</ul>
Can someone please tell me how I can get the users data and display it in a Grid View on the View? I was thinking that I could have a model that stores the data in which is then passed to the view and this model could be updated by the controller? But im unsure which datatype could be used to store the users data and then passed to the Grid View?
Thanks :-)
Using MembershipUser directly in your views isn't a very pure MVC approach, IMO.
IMO you could create a specific ViewModel containing just the fields you need to pass between the controller and the view, like so:
public class UserViewModel
{
public Guid UserId {get; set;}
public string UserName {get; set;}
// etc
}
After fetching all your users in your controller, you can then project the MembershipUsers across to your UserViewModel objects, e.g. using LINQ or AutoMapper, and then pass this enumeration into the View.
public ActionResult Index()
{
var systemUsers = Membership.GetAllUsers();
var vmUsers = new List<UserViewModel>();
foreach (MembershipUser u in Membership.GetAllUsers())
{
vmUsers.Add(new UserViewModel()
{
UserId = (Guid)u.ProviderUserKey,
UserName = u.UserName,
// etc
});
}
return View(vmUsers);
}
I'm not sure what you mean by 'GridView' (this is MVC, not WebForms), but the MVC WebGrid should do fine:
#model IEnumerable<MyNamespace.UserViewModel>
#{
var grid = new WebGrid(Model);
}
<div id="grid">
#grid.GetHtml()
</div>
Obviously you'll want to tweak the look and feel a bit.
I'm trying to figure out how to pass a model across views
Here is what I'm doing, I have a Register, RegisterConfirm, RegisterComplete views.
User starts at Register, fills info, clicks continue and posts to RegisterConfirm where they click checkbox to agree to privacy policy then post to RegisterComplete which creates the user based on the model in the first Register view.
Code:
[GET("Account/Register")]
public ActionResult Register()
{
return View();
}
[POST("Account/Register/Confirm")]
public ActionResult RegisterConfirm(RegisterModel model)
{
if (ModelState.IsValid)
{
return View(model);
}
else { return View("Register", model); }
}
[POST("Account/Register/Complete")]
public ActionResult RegisterComplete(RegisterModel model, bool agree)
{
if (agree) {
// Create User
}
return View("Register", model);
}
View Form
Register:
#using (Html.BeginForm("RegisterConfirm", "Account", FormMethod.Post, new { #id = "create" }))
{
Register Confirm:
#using (Html.BeginForm("RegisterComplete", "Account", FormMethod.Post, new { #id = "create" }))
{
Problem is, when I'm getting to RegisterComplete, model values are empty... any ideas or is this not possible? Or should this work and I need to double check my registercomplete?
Is your RegisterConfirm view using display-only elements to show the registration information? If so, MVC won't be able to bind the data to populate the model.
You need to render the model as Input elements, even if they're hidden, so that the model binder can populate RegisterModel (you can render the properties as both hidden elements for 'data retention' and output elements for display).
If you are using Input elements, check that the names of those elements match the property names of RegisterModel, otherwise, again, the model binder won't be able to populate RegisterModel.
If you can't, for whatever reason, put the data in Input elements in your RegisterConfirm view, you'll need to store the data somewhere server-side, either in Session State (or TempData, which uses Session State anyway) or in a database of some description.
The advantage with storing the model server-side is that you can be sure that the data hasn't been tampered with between sending it to the client and receiving it back.
You can use TempData and store your model inside it and receive your model back from it
[POST("Account/Register/Confirm")]
public ActionResult RegisterConfirm(RegisterModel model)
{
if (ModelState.IsValid)
{
//store data for any other request
TempData["newUser"]=model;
return View();
}
else { return View("Register", model); }
}
[POST("Account/Register/Complete")]
public ActionResult RegisterComplete(RegisterModel model, bool agree)
{
//retrieve data back irrespective of use choice
//to clear memory
RegisterModel newUser= TempData["newUser"];
if (agree) {
// Create User
//use newUser
}
return View("Register", model);
}
I am just getting into .NET MVC2 (.NET in general even) and I am having a hard time getting familiar with the "flow" of things. The MVC framework, I get.. for the most part. The part that I am getting tripped up on is applying standard programming practices to .NET MVC.
For example
public ActionResult Index()
{
var dataContext = new SiteContentDataContext();
var data = from c in dataContext.SiteContents where c.Slug == "home-page" select c;
// call/return getMainNav()
// call/return getSubNav()
return View(data);
}
public ActionResult SiteContent(string strSlug)
{
var dataContext = new SiteContentDataContext();
var data = from c in dataContext.SiteContents where c.Slug == strSlug select c;
// call/return getMainNav()
// call/return getSubNav()
return View(data);
}
private void getSubNav()
{
// get subnav records from db.
// return subnav records.
}
private void getMainNav()
{
// get main nav records from db.
// return main nav records.
}
The Index and SiteContent view are identical except for the fact that the Index view uses a different master page. Both views have a subnav and a main nav that will be dynamic content from a database.
The question, finally, is how would I go about populating the getSubNav and getMainNav functions and second, how would I return that data to the view properly?
Look into ViewModel objects, objects you create whose purpose in life is to carry data to and from your Views. The Models folder created for you by default in a new MVC project would hold exactly those classes.
You have options besides the ViewModel object methodology, but none are as clean. (The ViewData dictionary is around to help but it's not intended to be the primary means of providing data to your views.) Here's an example of how to set the Model property of the ViewData object to an instantiated, populated viewmodel instance:
public ActionResult SiteContent(string strSlug) {
SiteContentVM model = new SiteContentVM();
SiteService siteService = new SiteService();
model.Slug = siteService.GetALittleSlimyCreature(strSlug);
model.List1 = siteService.GetList1();
model.List2 = siteService.GetList2();
ViewData.Model = model;
return View();
}
You can now create a strongly typed view (complete with intellisense) to reference any properties of your ViewModel object instance from within your view simply through the Model property:
<% foreach (var item in Model.List1) { %>
<% Html.Encode(item.StringField) %> <!-- <= writing a property -->
<% Html.RenderPartial("PartialNameHere", item); %> <!-- <= the equivalent of a usercontrol -->
<% } %>
As you see above the process of getting data from the database does not change for MVC. You do it just as you would in a normal webforms project. (...usually this involves instantiating a business logic or service class of some sort rather than actually writing the code in the MVC project.)
Enjoy MVC!
You should look into DDD and TDD for ASP.NET MVC. For the looks of it you seem to be using Linq To Sql. I'm going to try to explain in a few words what I do to accomplish a good architecture.
Architecture
DB Context
Domain Model
Repository Pattern
It's good practice not to tie your Database Context with your Controllers. What you want to do is have your Controllers call your Repository, which in turn will return your Model. Now here's the tricky part you must convert the DB Context Objects into your Model Objects.
Imagine you have an Products table which Linq To SQL will give you as the Products Class.
That Products Class is part of the DB Context and what you want to do is alienate your context, in fact your Controllers won't even know it exists.
Why would I need a Model when I have Linq To SQL?
Well for starters LTS will regenerate all Objects everytime you change your Database meaning you wont have the ability to make change to the DB Context. And also you want to be able to use ASP.NET MVC 2 Annotations for validation and more.
Now create a Products Class for your Model
namespace MvcApplication.Models
{
public class Product
{
public int Id { get; set; }
[Required]
[StringLength(10)]
public string Name { get; set; }
[Required]
public string Description { get; set; }
[DisplayName("Price")]
[Required]
[RegularExpression(#"^\$?\d+(\.(\d{2}))?$")]
public decimal UnitPrice { get; set; }
}
}
Now you see this Class is part of the Model totally disconnected from the DB Context. Now what we do next is create our Repository Class.
namespace MvcApplication.Repository
{
public class AppRepository {
DbContext _context = new DbContext();
public IQueryable<Products> GetProducts()
{
return from p in _context.Products
select new Product {
Name = p.Name,
UnitPrice = p.UnitPrice
}
}
}
}
Now in your Controller you just call GetProducts();
public ActionResult SiteContent(string strSlug)
{
var repository = new AppRepository();
return View(repository.GetProducts());
}
Pretty isn't it.
You can use AutoMapper to map your DB Context objects to your Model objects.
http://automapper.codeplex.com/
Well ,,, i think what you are looking for here is Partial Views.
You can embed the MainNav & SubNav Views into your SiteContent View.
here's how this goes.
create your MainNav & SubNav as partial views.
in your SiteContent view use the Html.RendarPartial Method to include the other two views.
<%= Html.RenderPartial("MainNav", Model); %>
<%= Html.RenderPartial("SubNav", Model); %>
Now to the remaining part about how to get the data to the MainNav & SubNav views. Now is a good time to get familiar with ViewModels. View models are nothing but classes with some properties that you want to give to a view to display.
In your case i would create 3 view models.
SiteContentViewModel contains the content that will be displayed in your page.
MainNavViewModel contains the data that will be displayed insdie the MainNav.
SubNavVIewModel contains the data that will be displayed insdie the SubNav.
then i would include the MainNavViewModel & SubNavVIewModel inside the SiteContentViewModel.
(if you are sure that every SiteContent View will have a MainNav & a SubNav )
Now it's up to you to fill each view model with that data that you need.
here's how the code will look like.
public class SiteContentViewModel {
public MainNavViewModel MainNav { get; set;}
public SubNavVIewModel SubNav { get; set;}
// Any Other Data Needed In The SiteContent View (ex. PageTitle)
}
public class MainNavViewModel {
// Any Data Needed In The MainNav View
}
public class SubNavVIewModel {
// Any Data Needed In The SubNav View
}
Now back to the Partial Views ,,, using the View Models we created we can include the partials like this.
<%= Html.RenderPartial("MainNav", Model.MainNav); %>
<%= Html.RenderPartial("SubNav", Model.SubNav); %>
one important thing is to make our views strongly typed.
SiteContent view of type SiteContentViewModel
MainNav view of type MainNavViewModel
SubNav vIew of type SubNavViewModel
and in your SiteContent action method you will do something like this
// Initialize the ViewModels.
SiteContentViewModel model = new SiteContentViewModel();
model.MainNav = new MainNavViewModel();
model.SubNav = new SubNavVIewModel();
// Get Data From DB and set the properties that you created in your view models.
// examples.
model.PageTitle = // some data from db.
model.MainNav.(Some Property) = // some data from db.
model.SubNav.(Some Property ) = // some data from db.
return View(model);
hope that helps ... for more information you can see this link
In this code in an Edit view, the correct vendor name text appears but it is not validated when I blank its textbox and push Save. The Vendor is a property of the Order model, and VendorName is a property in the Vendor model. They relate referentially. My form does not all input into a single table, but on satellite tables as well.
<%= Html.TextBox("Vendor.VendorName")%>
<%= Html.ValidationMessage("Vendor.VendorName")%>
Why is validation not occuring?
This seems to work, but it seems like a hack to me:
using M = HelloUranus.Models
//...
namespace HelloUranus.Controllers
{
public class OrderDetailController : Controller
{
//...
private M.DBProxy db = new M.DBProxy();
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Edit(int id, FormCollection collection)
{
//...
var orderDetail = db.GetOrderDetail(id);
//...
try
{
if (string.IsNullOrEmpty(Request.Form["Vendor.VendorName"]))
{
throw new Exception();
}
UpdateModel(orderDetail);
db.Save();
return RedirectToAction("Details", new {id = orderDetail.odID } );
}
catch
{
ModelState.AddRuleViolations(orderDetail.GetRuleViolations());
return View(orderDetail);
}
//...
}
//...
}
Did you write any validation code? You have to manually validate it in your controller. If you:
ModelState.IsValid = false;
in the controller, for example, you will see some validation. That will trigger the ValidationSummary on the View to be shown. To actually add a validation to a single form element, use:
ModelState.AddModelError("Vendor.VendorName", string.Format("Vendor name must be at least {0} characters.",10));
Note that this will also set the ModelState to an invalid state and thus trigger the ValidationSummary as well.