Is there a way to get a reference for the ViewPage that contains a parital view from the partial view ??
Not 100% but I don't think this is possible. What specifically do you want to reference in the ViewPage from the Partial?
Couldn't you just share a Model between the ViewPage and ViewUserControl?
It seems there is no standard property for this so you should pass ViewPage object to partial view yourself:
<% Html.RenderPartial("partial_view_name", this); %>
The absolute answer: NO
You need to use ViewData or Model to share it.
My solution was a baseclass for any models used by partial controls.
It's useful for the times when you need to specify a model but want the partial view to have access to certain things from the containing View's model.
Note: this solution will support a hierarchy of partial views automatically.
Usage:
When you call RenderPartial supply the Model (for the view).
Personally I prefer this pattern, which is to create a view in place on the page consisting of whatever the patial view might need from the parent model.
I create a ProductListModel from the current model, which makes the parent model available easily to the partial view.
<% Html.RenderPartial("ProductList", new ProductListModel(Model)
{ Products = Model.FilterProducts(category) }); %>
In the partial control itself you specify the ProductListModel as a strongly typed view.
<%# Control Language="C#" CodeBehind="ProductList.ascx.cs"
Inherits="System.Web.Mvc.ViewUserControl<ProductListModel>" %>
Model class for the partial view
Note: I'm using IShoppingCartModel to specify the model to avoid coupling from the partial back to the containing view.
public class ProductListModel : ShoppingCartUserControlModel
{
public ProductListModel(IShoppingCartModel parentModel)
: base(parentModel)
{
}
// model data
public IEnumerable<Product> Products { get; set; }
}
Baseclasses:
namespace RR_MVC.Models
{
/// <summary>
/// Generic model for user controls that exposes 'ParentModel' to the model of the ViewUserControl
/// </summary>
/// <typeparam name="T"></typeparam>
public class ViewUserControlModel<T>
{
public ViewUserControlModel(T parentModel)
: base()
{
ParentModel = parentModel;
}
/// <summary>
/// Reference to parent model
/// </summary>
public T ParentModel { get; private set; }
}
/// <summary>
/// Specific model for a ViewUserControl used in the 'store' area of the MVC project
/// Exposes a 'ShoppingCart' property to the user control that is controlled by the
/// parent view's model
/// </summary>
public class ShoppingCartUserControlModel : ViewUserControlModel<IShoppingCartModel>
{
public ShoppingCartUserControlModel(IShoppingCartModel parentModel) : base(parentModel)
{
}
/// <shes reummary>
/// Get shopping cart from parent page model.
/// This is a convenience helper property which justifies the creation of this class!
/// </summary>
public ShoppingCart ShoppingCart
{
get
{
return ParentModel.ShoppingCart;
}
}
}
}
Related
I need to create a view that displays Order Header information and I need to add a patial view that displays a grid of Line Items. The partial view will be strongly typed from a viewmodel. I understand that I should use the html helper #Html.Partial("Path/view"). I have only used controllers up til now to open a view, populating the viewmodel before sending it to the view. Since the partial view is being called form the html helper, I would like to know is what would be the best way to populate the parital view with the model data.
Option 1: Inherit from parent page
By default, any partial view rendered by calling #Html.Partial("PartialViewName") will get the view model passed to the parent view.
So if you have:
View Model
namespace MyNamesapce
{
public OrderInfoViewModel
{
public string OrderTitle { get; set; }
public IEnumerable<OrderItem> OrderItems { get; set; }
}
}
OrderInfo.cshtml
#model MyNamespace.OrderInfoViewModel
<h1>#Model.OrderTitle</h1>
#Html.Partial("OrderLineItems")
The OrderLineItems page should get a MyNamespace.OrderViewModel passed to it... so your partial view should look like this:
OrderLineItems.cshtml
#model MyNamespace.OrderInfoViewModel
foreach (var orderItem in Model.OrderItems)
{
//Do stuff
}
Option 2: Specify model
You can use the second parameter to specify the view model to be passed. I.e.
OrderInfo.cshtml
#model MyNamespace.OrderInfoViewModel
<h1>#Model.OrderTitle</h1>
#Html.Partial("OrderLineItems", Model.OrderItems)
OrderLineItems.cshtml
#model IEnumerable<OrderItem>
foreach (var orderItem in Model)
{
//Do stuff
}
Option 3: Use partial actions
If you need to reuse a partial view over multiple pages, it could be a good idea to use a partial view to eliminate having to populate different view models with the same info just because the page is going to be using the same partial.
E.g.
View Model
namespace MyNamesapce
{
public OrderInfoViewModel
{
public string OrderTitle { get; set; }
}
}
Controller
public class OrderController : Controller
{
public ActionResult OrderInfo(int orderId)
{
OrderInfoViewModel viewModel = GetViewModel(orderId);
return View(viewModel);
}
public PartialViewResult OrderLineItems(int orderId)
{
IEnumerable<OrderItem> orderItems = GetOrderItems(orderId);
return Partial(orderItems);
}
}
OrderInfo.cshtml
#model MyNamespace.OrderInfoViewModel
<h1>#Model.OrderTitle</h1>
#Html.Action("OrderLineItems")
OrderLineItems.cshtml
#model IEnumerable<OrderItem>
foreach (var orderItem in Model.OrderItems)
{
//Do stuff
}
With a partial view, you are just sending in a Model just like you would with a normal View. For example, if your Model has a property of LineItem objects named 'LineItems' you simply would do this:
#Html.Partial("_PartialName", Model.LineItems)
Now if your Model does not have that property, you can either add it, or pass it another way, like ViewBag (I prefer a strongly typed method, but that is my opnion:
#Html.Partial("_PartialName", (List<LineItem>)ViewBag.LineItems)
These are not the only ways, but they are my preferred methods.
I am using a viewmodel with required field validation specified for some properties. I was able to create readonly version of same model using "displayfor". In a page, along with this readonly view there are other controls too along with submit. Now, when I click on "submit", it is getting validated and ModelState is invalid. How to remove validation, if we use model only for display.
ViewModel
public class CustomerVM
{
[Required]
public string Name {get;set;}
}
View
#using (Html.BeginForm("CreateCustomer, "Customer", FormMethod.Post))
{
#Html.DisplayFor(o => o.Name)
#..other input controls.#
<input id="btnSave" type="submit" value="Save" />
}
Model.State is invalid since name is rendered as label and httppost doesn't have that value.
This is where MetadataTypeAttribute comes in handy:
public class MyModel
{
public string Name { get; set; }
}
public interface IMyModelValidation
{
[Required]
public string Name { get; set; }
}
[MetadataType(typeof(IMyModelValiation))]
public class MyModelValidation : MyModel { }
Now MyModel has no validation and MyModelValidation does have validation, and they can be used almost interchangeably.
MetadataType
The MetadataTypeAttribute attribute enables you to associate a class with a data-model partial class. In this associated class you provide additional metadata information that is not in the data model.
For example, in the associated class you can apply the RequiredAttribute attribute to a data field. This enforces that a value is provided for the field even if this constraint is not required by the database schema.
You could use a different view model with different validation requirements for this 'read only' view. Or you could use the ModelState.Remove() method in your controller to get rid of errors against properties that you don't want validated. IMO the separate view model approach is better.
edit after seeing your code
Add a hiddenfor
#Html.DisplayFor(o => o.Name)
#Html.HiddenFor(o => o.Name)
That will pass the data back to the controller on the post and result in ModelState.IsValid == true
I'm not saying this is the best approach but I had to do something similar so I setup validation groups. I created an attribute that I placed on each model property that defined its validation group. Then on postback I called an extension method on the ViewDataDictionary and passed in the validation group that I wanted to run validation on. This would remove any validation messages for all other groups. Here is some example code:
The attribute:
/// <summary>
/// Attribute that assigns the property on a model to a given
/// validation group. By using the ValidationGroupExtension
/// and calling ValidateGroup on the ViewData validation errors
/// for properties that are not included in the validation group
/// will be removed.
/// </summary>
public class ValidationGroup : Attribute
{
/// <summary>
/// The unique name of the group.
/// </summary>
public String Group { get; set; }
public ValidationGroup(String group)
{
this.Group = group;
}
}
The extension:
/// <summary>
/// Used in conjunction with the ValidationGroup attribute to
/// specify which fields in a model should be validated. The
/// ValidateGroup extension should be called on ViewData before
/// checking whether the model is valid or not.
/// </summary>
public static class ValidationGroupExtension
{
/// <summary>
/// Remove all validation errors that are assocaited with
/// properties that do not have the ValidationGroup attribute
/// set with the specified group name.
///
/// This only handles flat models.
/// </summary>
/// <param name="viewData">View Data</param>
/// <param name="model">Data model returned</param>
/// <param name="group">Name of the validation group</param>
/// <returns></returns>
public static ViewDataDictionary ValidateGroup(this ViewDataDictionary viewData, Object model, String group)
{
//get all properties that have the validation group attribut set for the given group
var properties = model.GetType().GetProperties()
.Where(x => x.GetCustomAttributes(typeof(ValidationGroup), false)
.Where(a => ((ValidationGroup)a).Group == group).Count() > 0)
.Select(x => x.Name);
//find all properties that don't match these properties
var matchingProperties = viewData.ModelState.Where(x => !properties.Contains(x.Key)).ToList();
//remove any property that isn't in the gorup
foreach (var matchingProperty in matchingProperties)
{
viewData.ModelState.Remove(matchingProperty.Key);
}
return viewData;
}
}
On PostBack:
ViewData.ValidateGroup(model, "my validation group name");
if (ModelState.IsValid)
{
...
}
On the ViewModel:
[Required]
[DisplayName("Name")]
[ValidationGroup("my validation group name")]
public string Name { get; set; }
Every page in my site should have some of the same data, similar to how in SO every page displays info about the current user at the top of the page. The way I implemented this was to have a base controller class that all my controllers derive from. In that base controller's constructor I put my model in the ViewBag, and then my layout page uses that.
I'm running into problems with this because my layouts aren't strongly typed. For example, I have to construct new HtmlHelpers in the layout page:
#{var layoutHtml = new HtmlHelper<LayoutModel>(Html.ViewContext, Html.ViewDataContainer); }
#* OK, now we can use the html helper... *#
#layoutHtml.TextAreaFor(model => model.Feedback)
I really don't want to have to have my models subclass from a layout model, since that would force each action to fill out the shared model data individually, but manually creating HtmlHelpers also seems like a bad idea.
Any thoughts?
I might be wrong, but shouldn't you use partial views for this?
Your solution looks way too complicated.
A base controller is a great way to start. I would also introduce a base viewmodel. The base viewmodel would contain user specific information.
public abstract class BaseController : Controller
{
public string CurrentAccountName
{
get
{
return (HttpContext.User == null) ? null : HttpContext.User.Identity.Name;
}
}
public T CreateViewModel<T>() where T : BaseViewModel, new()
{
T viewModel = new T
{
UserName = CurrentAccountName
};
return viewModel;
}
}
public abstract class BaseViewModel
{
public string UserName { get; set; }
}
Then on each request you would populate your viewmodel with user specific information and whatever information is required for the view. MyViewModel is just a viewmodel that inherits from BaseViewModel.
public class MyController : BaseController
{
public ActionResult Index()
{
MyViewModel viewModel = CreateViewModel<MyViewModel>();
return View(viewModel);
}
}
In the master view I would pass in the BaseViewModel and the View I would pass in the inherited MyViewModel.
Now you have access to your user information in your master view and can pass it to a partial or render it directly to the page.
you can define multiple Layouts which you can use in your appropiate views! Just include them like so:
#{
Layout = "~/Views/Shared/_MySubLayout.cshtml";
}
i believe you can use RenderAction to solve this problem. Because information this action will display is common on all pages, you can put it in BaseController and call it from your site master. it will compute its own model and return that model to partial view which can be strongly typed and you don't have to instantiate htmlHelper the way you are doing now.
Consider the following scenario:
Action Edit() is forwarded to Edit.aspx view to render the view.
Edit.aspx consists of textbox1 and two partial views (aka view user controls):
part1.ascx (which has textbox2, textbox3)
and part2.ascx (which has checkbox1 and checkbox2)
You want to have a strongly typed view for Edit.aspx, say, you use EditViewData class.
You also need Edit.aspx, part1.ascx and part2.ascx have access to some global information such as currentUserID, currentUserLanguage, currentUserTimezone.
Questions:
How do you go about structuring the EditViewData class?
How do you pass the view data to the view and partial views so that the object gets populated automatically when you submit the form and return to the Edit() http.post action?
What do you pass to the Edit() http.post action?
Your viewdata should look like this:
public class EditViewData
{
public int currentUserID { get; set; }
public string currentUserLanguage { get; set; }
public string currentUserTimezone { get; set; }
// ... other stuff
}
After you strongly type your aspx, you also need to strongly type your ascxs. Then in your aspx, when you call RenderPartial, just call like usual:
<% using (Html.BeginForm()) %>
<% Html.RenderPartial("part1.ascx" ); %>
<% Html.RenderPartial("part2.ascx" ); %>
<%}%>
It should automatically inherit the Model in the control. Just remember that your BeginForm should be surrounding both of your controls (ascxs).
I want to pass some parameters to my MVC UserControl like ShowTitle(bool) and the ViewData.Model.Row . How I define my usercontrol and pass them to it?
Tanx
You can use the RenderAction HtmlHelper, found in the MVC futures dll available at codeplex. In your main page
...
You need an action method on the controller with the parameters. The action then creates a ViewData for the usercontrol. The usercontrol is returned as a view with
return View("usercontrolname", model);
The .ascx file then uses the model for just the user control. The resulting HTML is rendered into the calling page.
You can define your control as
public partial class MyUserControl : System.Web.Mvc.ViewUserControl<MyUserControlViewData> {
}
public class MyUserControlViewData {
public IList<MyData> MyData { get; set; }
public string SomethingElse { get; set; }
}
After that you can create an instance of MyUserControlViewData classin your controller, populate with data and pass it to the view. Is that what you're looking for?