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.
Related
In ASP.NET MVC I'm thinking to implement a method that converts data to JSON. However, I'm not sure where to add this method. Should I add it in Controller, service class, or Model? So, for example, let's say I have a model named book and a controller named Library.
public class Book
{
public string Name { get; set; }
public string Json { get; set; }
}
public class Library: Controller
{
public ActionResult Book(string bookName)
{
return View();
}
}
Should I add a private method like JsonConverter(object jsonToConvert) in Book class and call it in the constructor of Book? So, a user will pass an object to be converted to Book class, and then when instantiated it will be converted to Json and assigned to Book.Json.
Is adding methods in a model a bad practice? Is it better to add a service class that has JsonConverter and do the conversion in the controller?
So, like
public class Library: Controller
{
public ActionResult Book(string bookName)
{
var book = GetBook(bookName)
var json = Service.ConvertJson(book.object)
return View(book, json);
}
}
To sum up my question: is it bad practice to add private methods in a model and manipulate data format? The reason why I don't want to do the conversion in the controller is that I think it is a bad practice to convert data format in a class as the data layer is Model. Am I wrong?
The idea is to keep layers abstract from each other, and keep in mind Single-Responsibility principle, a class should do one thing, and one thing well.
In that regard, your model is responsible for defining the data schema. An instance of a model would hold the data. And that's it.
So this is your model
public class Book
{
public string Name {get; set;}
public string Json {get; set;}
}
In this case outputting an instance of a Book is an endpoint concern. It's not related to your business logic which concerns your services.
So keeping it in the controller like
public class Library: Controller
{
public ActionResult Book(string bookName)
{
var book = GetBook(bookName)
var json = Service.ConvertJson(book.object)
return View(book, json);
}
}
is what you can do best.
Whenever you are trying to achieve something ask yourself: Whose responsibility should this be?
Controller: Think of this like the waiter who greets you, shows you to your table and gets your order in a restaurant.
Service: This is the chef who cooks your meal.
Model: This is the meal you are going to order in the menu.
Converting to JSON is related to serving, so this responsibility goes to the controller.
I have the following scenario:
public class Stay
{
[Contained]
public Guest PrimaryGuest {get;set;}
}
public abstract class Guest
{
public int ID {get; set;}
}
public class EntityGuest : Guest
{
public string EntityName {get;set;}
}
public class PersonGuest : Guest
{
public string SurName {get;set;}
public string GivenName {get;set;}
}
When querying for the stays, I wish to order by a PersonGuest/SurName.
I know how to order by a child property: [URL]/Stays?$expand=PrimaryGuest&$orderby=PrimaryGuest/ID - but how would I order by on a child property that is derived? Is it even possible? I could not determine it by the OData documentation - it wasn't at least called out for contained entities.
This answer helped me a lot in a similar scenario: oData $expand on Derived Types
Basically you can 'Cast' any complex or entity typed property in your query by adding a forward slash and the qualified name of the model type, using the namespace you have defined for your model, not the .Net full type name.
[URL]/Stays?$expand=PrimaryGuest&$orderby=PrimaryGuest/ModelNamespace.PersonGuest/Surname
If you are unsure of the model namespace, look at the model builder code, or use something similar to this:
ODataConventionModelBuilder builder = new ODataConventionModelBuilder();
builder.Namespace = "MyAppModel";
Then your URL should look like this:
[URL]/Stays?$expand=PrimaryGuest&$orderby=PrimaryGuest/MyAppModel.PersonGuest/Surname
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.
I have a view which contains data from 4 tables from the database.
Do i need to define all of those fields in my model? so that i can use them in my view like :-
#model.fieldFromTable1
#model.fieldFromTable2
#model.fieldFromTable3
#model.fieldFromTable4
The quick answer is yes. You can probably think of your model more as a ViewModel. Not an actual model from your database.
In your controller you would just populate the ViewModel from your database models
MyViewModel.cs
//put whatever properties your view will need in here
public class MyViewModel
{
public string PropertyFromModel1 {get; set;}
public string PropertyFromModel2 {get; set;}
}
MyController.cs
public ActionResult MyAction()
{
//the only job your action should have is to populate your view model
//with data from wherever it needs to get it
Model1 model = GetFirstModelFromDatabase();
Model2 model2 = GetSecondModelFromDatabase();
MyViewModel vm = new MyViewModel
{
PropertyFromModel1 = model.MyProperty;
PropertyFromModel2 = model2.MyProperty;
}
return View(vm);
}
MyAction.cshtml
#Model.PropertyFromModel1
#Model.PropertyFromModel2
It's actually pretty standard practice to not use your raw domain models in your views, because I would say that typically don't match up exactly to what you want to display.
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