My main view uses
#model IEnumerable<Ortund.Models.Reward>
I'm trying to allow the user to do several things on this view without having to navigate away.
Specifically, I want the user to be able to do the following:
View rewards he/she has already claimed (rewards that this user is eligible to redeem)
Claim a new receipt and in so doing, add the reward associated with that receipt to his/her current rewards
Redeem part of the rewards that he/she is eligible for, or all of them at once
I'm using partial views to achieve this as I can set a new model for each partial.
It looks something like this:
/Home/Index
if (Request.Cookies["Ortund"] == null)
{
// render a login form
}
else
{
<p>#String.Format("Welcome, {0}!", Convert.ToString(Request.Cookies["Ortund"]["username"])) <a id="claim-link">Claim New</a> | <a id="redeem-link">Redeem</a></p>
#Html.Partial("_RewardsView")
<!-- Render the new claim and redemption views as well -->
<div class="claim-new">
#Html.Partial("_ClaimsView")
</div>
<div class="redemption">
#Html.Partial("_RedemptionView")
</div>
_RewardsView
#model IEnumerable<Ortund.Models.Reward>
....
_ClaimsView
#model Ortund.Models.Receipt
....
_RedemptionView
#model IEnumerable<Ortund.Models.Reward>
....
I understand that view models are the preferred approach, but as I haven't yet worked out how to correctly use one, I'm going with this approach.
I've done this on another project, but this time I'm getting an error saying that the dictionary time that the view requires is different to the one being supplied (in this specific instance, we're getting confusion between Receipts and Rewards).
I'm not exactly sure what to do about this except to build the forms manually with no associations to the models, but rather to post to the correct controller...
What if you use one model, but build like this:
public class MainModel{
public fakeOneModel fakeModelOneView{get; set;}
public fakeTwomodel fakeModelTwoView{get; set;}
public fakeThreemodel fakeModelThreeView{get; set;}
}
public class fakeOneModel{
public string objectA1 {get; set;}
public string objectA2 {get; set;}
public string objectA3 {get; set;}
}
public class fakeTwoModel{
public string objectB1 {get; set;}
public string objectB2 {get; set;}
public string objectB3 {get; set;}
}
public class fakeThreeModel{
public string objectC1 {get; set;}
public string objectC2 {get; set;}
public string objectC3 {get; set;}
}
Then from your views, you can access all classes from one models like:
#Html.LabelFor(m=>m.fakeModelOneView.objectA1 )
#Html.TextBoxFor(m=>m.fakeModelOneView.objectA1 )
#Html.LabelFor(m=>m.fakeModelTwoView.objectB1 )
#Html.TextBoxFor(m=>m.fakeModelTwoView.objectB1 )
#Html.LabelFor(m=>m.fakeModelThreeView.objectC1 )
#Html.TextBoxFor(m=>m.fakeModelThreeView.objectC1 )
By default, a partial view rendered by calling #Html.Partial("PartialViewName") takes the view model of the parent view.
The model for your main page should include the models that you'll pass on to the partial views:
Model:
public class IndexModel
{
public Ortund.Models.Receipt Receipt { get; set; }
public IEnumerable<Ortund.Models.Reward> ClaimedRewards { get; set; }
public IEnumerable<Ortund.Models.Reward> EligibleRewards { get; set; }
}
View:
When you call the partial view, specify the model you'll pass to it, such as
#model IndexModel
#Html.Partial("_RewardsView", Model.ClaimedRewards)
#Html.Partial("_ClaimsView", Model.Receipt)
#Html.Partial("_RedemptionView", Model.EligibleRewards)
Having multiple forms on the single page is another issue. See
Multiple Forms in same page ASP.net MVC
So, in short: use a viewmodel?
Yes, essentially. There's so many times that view models will simply be required to achieve what you need that you might as well just learn to use them.
However, in this one circumstance, you can hold out just a bit longer as you can also achieve what you're looking for using child actions. Essentially, that would look something like this:
Controller
[ChildActionOnly]
public ActionResult Rewards()
{
// fetch rewards
return PartialView("_Rewards", rewards)
}
View
#Html.Action("Rewards")
If you want to use ajax to update the Views, then you can try MultiPartials. With the added benefit that they will update any elements you define with Ids, this means you can update multiple divs with data and not have to worry about managing complicated view Models.
#Ajax.ActionLink("ActionLink", "ActionLinkClick", new AjaxOptions { OnSuccess = "MultipartialUpdate" })
public ActionResult ActionLinkClick()
{
MultipartialResult result = new MultipartialResult(this);
result.AddView("_Div1", "Div1", new Div1Model("ActionLink clicked"));
result.AddView("_Div2", "Div2", new Div2Model("ActionLink clicked"));
result.AddContent("ActionLink", "LastClickedSpan");
result.AddScript("alert ('ActionLink clicked');");
return result;
}
Then in view
#using(Ajax.BeginForm("FormSubmit", new AjaxOptions { OnSuccess = "MultipartialUpdate" }))
{
<input type="submit" value="Submit">
}
This code snippet is from the above mentioned link, and you can create as many partials as possible without having to create complicated code to manage all of them.
Related
My app has a main dashboard which is comprised of 8 different partial views; each backed by their own view model and in my controller I'm just calling
public ActionResult mainDashboard(){
return View()
}
to return the dashboard. My question is would it be recommended to create a dashboard view model that also contains references to the view models of the partial views? What's considered a recommended best practice in this situation?
Ohkk here is a good idea as well to use html.Action instead of html.partial
This would look more like this:
public ActionResult Dashboard()
{
return View();
}
public PartialViewResult Clubs()
{
....
return PartialView(db.Clubs.ToList());//this assumes the partial view is named Clubs.cshtml, otherwise you'll need to use the overload where you pass the view name
}
public PartialViewResult Alerts()
{
....
return PartialView(db.Alerts.ToList());
}
Dashboard.cshtml
<div class="dashboard_alerts">
#Html.Action("Alerts")
<div class="dashboard_pending_clubs">
#Html.Action("Clubs")
</div>
<div class="dashboard_verified_members">
#Html.Action("Members")
</div>
OR
You would need to create a ViewModel specific for the Dashboard page precisely this would be more efficient way
public class DashboardViewModel
{
public IEnumerable<GMC.Models.Clubs> Clubs { get; set; }
public IEnumerable<GMC.Models.MemberUsers> Users { get; set; }
public IEnumerable<GMC.Models.Alerts> Alerts { get; set; }
}
Then, in the Dashboard action method, you would populate each list:
myModel.Users = db.MemberUsers.ToList();
...
You would then need to update the view to take in this new ViewModel
#model DashboardViewModel
Finally, from within the view, you would need to pass in the data to each partial:
#Html.Partial("DashboardAlerts", Model.Alerts)
#Html.Partial("DashboardClubs", Model.Clubs)
So, lets look at an example. I have a Customer view model that looks like this:
public class CustomerViewModel {
public string Name {get; set;}
public int CustomerTypeId {get; set;}
}
On the UI there needs to be a drop down of customer types. In order to populate this, something needs to go to the business layer or data layer and grab this list of customer types.
It seems to me that this logic doesn't really belong in CustomerViewModel since it isn't really customer data; only CustomerTypeId is. So my question is, where in general (not necessarily in .net MVC, but general practice for the MVC view model concept) do people put this data access?
Is it acceptable to have a function in the view model itself called GetCustomerTypes()?
public class CustomerViewModel {
public string Name {get; set;}
public int CustomerTypeId {get; set;}
public List<CustomerType> GetCustomerTypes() { ... }
}
Should I make a class/method in the business layer or a helper and import the code manually in the view to access this function? It seems to me like this would be code that clutters up the view and doesn't belong there.
#{
using My.App.CustomerTypeHelper;
var helper = new CustomerTypeHelper();
var customerTypes = helper.GetCustomerTypes();
}
...
#Html.DropDownFor(x => x.CustomerTypeId, customerTypes)
The global Html helper eases this problem a bit in .Net, but I am looking for a more global solution conceptually that I can apply to PHP code, etc also. PHP doesn't have the ability to cleanly have a static class that can be separated into small organized units with extension methods. So any global helper is likely to get large and ugly.
You should include List<CustomerType> in the model but do NOT implement function inside the model. Set the data from controller instead.
Something like this:
public class CustomerViewModel {
public string Name {get; set;}
public int CustomerTypeId {get; set;}
public List<CustomerType> CustomerTypes { get; set; }
}
Assign data to ViewModel from Controller:
var model = new CustomerViewModel();
model.CustomerTypes = Customer.GetCustomerTypes();
You could have a class that is 'shared' between models that has all the definitions of such data. I would go with something such as:
public static partial class StaticData
{
public static List<CustomerType> CustomerTypes = new Lisr<CustomerType>
{
new CustomerType { Name = "Whatever", Discount = 10, ....... },
new CustomerType { Name = "Another", Discount = 0, ........}
// etc
}
}
Notice this is a partial class so you can split this across files/folders in you project and have:
CustomerTypes.cs
SupplierTypes.cs
ProductTypes.cs
And anything else as separate files all building into a shared StaticData class that end up containing all your definitions for drop-downs and any other non-database information.
So then, in your view, you can populate your select options using StaticData.CustomerTypes.
The CustomerModel (not ViewModel, because there is no ViewModel in MVC) is not the model of a Customer. It is the model used in the view Customer. If that view needs this information, it should be in that model.
It is not clear to me what that view does in your application, but it looks like some customer creation form. Just calling it CustomerModel doesn't explain the intent of the class very well. You might want to call your model CreateModel, used in Create.cshtml that is returned from the Create() method in the CustomerController. Then it makes sense to add the CustomerTypes to this model: you need to have CustomerTypes if you want to create a customer.
My MVC application has a handful of roles. ex Admin,General. I use a CustomRoleProvider but then in the view I do following
#if (Roles.IsUserInRole("admin"))
{
<div class="editor-label">#Html.RadioButton("selection", "View Project Details", false)View Project Details</div>
}
Recently I was told to additionally restrict access based on business logic ex. if createdby user on a Project was 'xyz',allow 'xyz' access to the link.
I know one way would be to check the controller and return different views based on the Roles and BusinessLogic. Thats going to be unmanageable!!
is there any other way to achieve this?
You can just store permissions in your model or in ViewBag and use if statements as above. You can also create method like IsInBuissnesRole and implement logic inside it.
I have answered similar question here.
Basically you should use viewmodel with properties denoting if certain features of the view should be enabled/visible to user. Then it is controller (maybe based onr some business logic service) should set these values.
Example:
// Model
public class Procuct
{
public int Id {get; set;}
public string Name {get;set;}
}
// Viewmodel
public class ProcuctViewModel
{
public int Id {get; set;}
public string Name {get;set;}
public bool CanEdit {get;set;}
public bool CanDelete {get; set;}
}
// somewhere in controller
var product = new ProductViewModel(_repo.GetProductById(1));
if (Roles.IsUserInRole("admin"))
{
product.CanEdit = true;
}
// ...
return View(product);
Then your view gets simpler and all the security stuff is moved to controller and is unittestable
I'm new to ASP.NET MVC so this could have an obvious answer. Right now I have a form in my view with a lot of input controls, so I have an action that looks like this:
public ActionResult MyAction(string formItemOne, int? formItemTwo, etc...)
It has like a dozen parameters, which is pretty ugly. I'm trying to change it to this:
public ActionResult MyAction(FormCollection formItems)
and then parse the items dynamically. But when I change to a FormCollection, the form items no longer "automagically" remember their values through postbacks. Why would changing to a FormCollection change this behavior? Anything simple I can do to get it working automagically again?
Thanks for the help,
~ Justin
Another solution is to use models instead of manipulating the raw values. Like this:
class MyModel
{
public string ItemOne { get; set; }
public int? ItemTwo { get; set; }
}
Then use this code:
public ActionResult MyAction(MyModel model)
{
// Do things with model.
return this.View(model);
}
In your view:
<%# Page Inherits="System.Web.Mvc.ViewPage<MyModel>" %>
<%= Html.TextBox("ItemOne", Model.ItemOne) %>
<%= Html.TextBox("ItemTwo", Model.ItemTwo) %>
To replace your big list of parameters with a single one, use a view model. If after the POST you return this model to your view, then your view will remember the values posted.
A view model is simply an class with your action parameters as public properties. For example, you could do something like this, replacing:
public ActionResult MyAction(string formItemOne, int? formItemTwo, etc...)
with
public ActionResult MyAction(FormItems formItems)
{
//your code...
return View(formItems);
}
where FormItems is
public class FormItems
{
public property string formItemOne {get; set;}
public property int? formItemTwo {get; set;}
}
You may see a complete example in Stephen Walter's post ASP.NET MVC Tip #50 – Create View Models.
Maybe because they aren't magically inserted into the ModelState dictionary anymore. Try inserting them there.
If you use UpdateModel() or TryUpdateModel() I think the values are gonna be persisted.
Consider this scenario:
class Book
{
int id;
List<Category> categories;
}
class Category
{
int id;
string name;
}
I have a strongly typed view for the class Book. When the librarian wants to edit the details of any Book, I want to display checkboxes with all possible categories. In case Book.categories contains a category, that check box will be checked.
The user can check and uncheck the boxes at will and then will click 'Submit'. I want that my controller function should be invoked with a parameter of type Book, with the new set of categories properly set.
How can this be done in ASP.NET MVC?
You might want to split this up into a couple of classes, one that is your book class, one for setting the view, and one for receiving the view's values. This may sound overdone but if nothing else it makes it so you aren't banging a square peg into a round hole. After all, if you just go with the Book class you have right now, you have to figure out how to make the view display everything you need (Say a bunch of check boxes for the categories... A list that your Book does not have completely) and then send it back to the controller still having to fit the same design as the Book class. To me that seems like in the long run a lot harder and maybe not possible without compromising the original book class design.
I would suggest something like this:
class ViewBookModel
{
public ViewBookModel(Book book, IList<Categories> categoryList)
{
BookId = book.Id;
BookName = book.BookName;
CategoryList = categoryList;
}
public Int32 BookId { get; private set; }
public String BookName { get; private set; }
IList<Categories> CategoryList { get; set; }
}
And for posting back to the controller:
class ViewBookInModel
{
public String BookId { get; set; }
public String BookName { get; set }
public Int32[] CategoryIds { get; set; }
}
And the markup would be something like:
<%
foreach(var category in Model.CategoryList)
{
%>
<input type="checkbox" name="CategoryIds" value="<%= category.Id %>" />
<%
}
%>
And the controller Method in:
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult UpdateBook(ViewBookInModel book)
It can be done many ways.
I'd suggest keeping your views simple. A view should perform one particular function. You'd have a view to see details of the book, then a view to edit the book. This will make it easier to create a model which provides the information needed to store the book and its categories.
Update:
I agree checkboxes are the best way. For how to handle them on the trip back, see this question: How to handle checkboxes in ASP.NET MVC forms?