MVC3 correct usage of model - asp.net-mvc-3

While looking at MVC3 examples of models. Most people tend to use model classes to create class definitions for business objects to hold data with very little or no logic. The sole purpose of model then is to be passed around. For example:
namespace MvcMusicStore.Models
{
public class Cart
{
[Key]
public int RecordId { get; set; }
public string CartId { get; set; }
public int AlbumId { get; set; }
public int Count { get; set; }
public System.DateTime DateCreated { get; set; }
public virtual Album Album { get; set; }
}
}
Is this how models classes should typically be used in MVC? Sometimes I see logic but are very specific to manipulating data. For exmaple:
public partial class ShoppingCart
{
MusicStoreEntities storeDB = new MusicStoreEntities();
string ShoppingCartId { get; set; }
public const string CartSessionKey = "CartId";
public static ShoppingCart GetCart(HttpContextBase context)
{
var cart = new ShoppingCart();
cart.ShoppingCartId = cart.GetCartId(context);
return cart;
}
// Helper method to simplify shopping cart calls
public static ShoppingCart GetCart(Controller controller)
{
return GetCart(controller.HttpContext);
}
public void AddToCart(Album album)
{
// Get the matching cart and album instances
var cartItem = storeDB.Carts.SingleOrDefault(
c => c.CartId == ShoppingCartId
&& c.AlbumId == album.AlbumId);
if (cartItem == null)
{
// Create a new cart item if no cart item exists
cartItem = new Cart
{
AlbumId = album.AlbumId,
CartId = ShoppingCartId,
Count = 1,
DateCreated = DateTime.Now
};
storeDB.Carts.Add(cartItem);
}
else
{
// If the item does exist in the cart, then add one to the quantity
cartItem.Count++;
}
// Save changes
storeDB.SaveChanges();
}
}
What is the correct way of using the Model? In classic MVC definition, model is where the intelligence of the application should be. However looking at MVC3 samples, a lot of logic is in controller or another layer for say Data Access. What is the advantage of this?
Thanks

The short answer is it provides for separation of model definitions and data access, which are conceptually different. When you separate your Data Access to its own layer (not as part of either controllers or models) you achieve far greater De-coupling.
That said there are a lot of different ways developers are using MVC, and model as data accessors is definitely one of them - the framework even supports models based on entity framework; go straight from the database to a usable model.
There's always the "fat controller" pattern of course; that is, stick all your handling logic inside the controller. I wouldn't recommend that because it will very quickly spiral into unmaintainable code.

Related

Can I add a view based on a ViewModel and without a controller for the newly added view?

I am newbie to MVC3 and I wonder if this is even possible and good practice?
I have a model + view + controller which works fine. This view shows a list of people - I want to be able to click on a person's name and be redirected to a new view that will show that persons details. This new view only has a ViewModel, but no controller because I plan to pass in the object in the action.
The Person object contains all the properties my view needs to show:
#Html.ActionLink(item.Person.FirstName, "PersonDetails", item.Person)
Is this possible/good practice??
I believe you have an misunderstanding of how MVC works. Your ActionLink will ALWAYS redirect to a corresponding ActionMethod of a Controller. What you'll want to do is create an action method in your controller that accepts the necessary parameters and then returns to the View your ViewModel.
Here is a very quick example to get you started:
public class HomeController : Controller
{
public ActionResult List()
{
return View();
}
public ActionResult DetailById(int i)
{
// load person from data source by id = i
// build PersonDetailViewModel from data returned
return View("PersonDetails", PersonDetailViewModel);
}
public ActionResult DetailByVals(string FirstName, Person person)
{
// build PersonDetailViewModel manually from data passed in
// you may have to work through some binding issues here with Person
return View("PersonDetails", PersonDetailViewModel);
}
}
Not a good way to do it like you want to (in your original post). A view should always have a view model. A view model represents only the data that you want to have on the view, nothing more and nothing less. Do not pass your domail model to the view, but rather use a view model. This view model might contain just a portain of the properties of your domain model.
In your list view you probably have a grid, and next to each row you probably have a details link, or a link on the name (as you have it). When either of these links are clicked then you are directed to a details view. This details view will have its own view model with only the properties that you need to display on the details view.
A domail model might look something like:
public class Person
{
public int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public int Age { get; set; }
public string ExampleProperty1 { get; set; }
public string ExampleProperty2 { get; set; }
public string ExampleProperty3 { get; set; }
}
Let say you only want to display the person's id, first name, last name and age then your view model will look like this:
public class PersonDetailsViewModel
{
public int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public int Age { get; set; }
}
You don't need ExampleProperty1, ExampleProperty2 and ExampleProperty3 because they are not required.
Your person controller might look like this:
public class PersonController : Controller
{
private readonly IPersonRepository personRepository;
public PersonController(IPersonRepository personRepository)
{
// Check that personRepository is not null
this.personRepository = personRepository;
}
public ActionResult Details(int id)
{
// Check that id is not 0 or less than 0
Person person = personRepository.GetById(id);
// Now that you have your person, do a mapping from domain model to view model
// I use AutoMapper for all my mappings
PersonDetailsViewModel viewModel = Mapper.Map<PersonDetailsViewModel>(person);
return View(viewModel);
}
}
I hope this clears things up a little more.

How do I maintain the type of items in a collection of a base type across postbacks?

I have a base class, a set of subclasses and a collection container that I am using to create and populate controls dynamically using partial views.
// base class
public class SQControl
{
[Key]
public int ID { get; set; }
public string Type { get; set; }
public string Question { get; set; }
public string Previous { get; set; }
public virtual string Current { get; set; }
}
// subclass
public class SQTextBox : SQControl
{
public SQTextBox()
{
this.Type = typeof(SQTextBox).Name;
}
}
//subclass
public class SQDropDown : SQControl
{
public SQDropDown()
{
this.Type = typeof(SQDropDown).Name;
}
[UIHint("DropDown")]
public override string Current { get; set; }
}
// collection container used as the Model for a view
public class SQControlsCollection
{
public List<SQControl> Collection { get; set; }
public SQControlsCollection()
{
Collection = new List<SQControl>();
}
}
I populate the Collection control with different subclasses of SQControl as required at runtime, and in EditorTemplates I have a separate view for each subclass. Using Html.EditorFor on the collection items I can dynamically generate the form with the appropriate controls.
This all works fine.
The problem I have is that when I save the form, MVC binding cannot tell what subclass each item in the Collection was created with and instead bind them to instances of the base class SQControl.
This confuses the View engine as it cannot determine the proper views to load anymore and simply loads the default.
The current workaround I have is to save the "Type" of the subclass as a field in the model, and on postback, I copy the collection into a new container, re-creating each object with the proper subclass based on the information in the Type field.
public static SQControlsCollection Copy(SQControlsCollection target)
{
SQControlsCollection newCol = new SQControlsCollection();
foreach (SQControl control in target.Collection)
{
if (control.Type == "SQTextBox")
{
newCol.Collection.Add(new SQTextBox { Current = control.Current, Previous = control.Previous, ID = control.ID, Question = control.Question });
}
else if (control.Type == "SQDropDown")
{
newCol.Collection.Add(new SQDropDown { Current = control.Current, Previous = control.Previous, ID = control.ID, Question = control.Question });
}
...
}
return newCol;
}
So my Question is, is there any better way to maintain the type of items in a base-typed-collection between postbacks? I understand it's practice in MVC to have a typed view for each model, but I want to be able to build a view on the fly based on an XML document using reusable partial views.

Code first DbContext with current user filter

I'm building an ASP.NET MVC3 website with an code first database and have the following question:
Is it possible to make an instance of MyDbContext class with an additional argument set which will be used for filtering the results of calls to mydbcontext.
I want to use this for restricting the resultset to the current user that is logged in on my asp.net mvc website.
Any directions would be great!
I don't see why that should be a problem. Something like this should work:
public class Northwind : DbContext
{
public DbSet<Product> Products { get; set; }
public DbSet<Category> Categories { get; set; }
}
public class FilteredNorthwind : Northwind
{
public IQueryable<Products> GetFilteredProducts(string userRole)
{
return Products.Where(product => product.UserRole == userRole);
}
}
Update
To make it impossible for your MyDbContext to be abused, you could put all your database code and models into a separate project/assembly. Then make your DbContext an internal class (instead of public), then create a public class (FilteredDbContext) that wraps your MyDbContext and exposes methods that allow you to only grab the data your allowed to see. Then in your main assembly (your web project), you will only be able to use FilteredDbContext.
So, for example:
internal class Northwind : DbContext // note: internal class
{
public DbSet<Product> Products { get; set; }
public DbSet<Category> Categories { get; set; }
}
public class FilteredNorthwind // note: does not inherit from `Northwind`
{
private readonly _dbContext = new Northwind();
public IQueryable<Products> GetProducts(string userRole)
{
return _dbContext.Products.Where(product => product.UserRole == userRole);
}
}
If Northwind and FilteredNorthwind are in a separate assembly from your web app, you can instantiate only FilteredNorthwind from your web app.
Update 2
If you use a ViewModel, then your web app can't get back to the list of all products for a category because you extract out only the properties you need (and only the properties the user is allowed to see).
public class ProductViewModel
{
public int Id { get; set; }
public string Name { get; set; }
public decimal Price { get; set; }
}
public IEnumerable<Products> GetProducts(string userRole)
{
return _dbContext.Products
.Where(product => product.UserRole == userRole)
.Select(product => new ProductViewModel
{
Id = product.Id,
Name = product.Name,
Price = product.Price
};
}
You could make a layer above and hide the generated one and create a your own DbContext which derives from the generated MyDbContext. Just a wild guess but it seems logical to me and so you can implement your own argument set and still use the generated one.
I would do this:
public interface IUserContext {
string User { get; set; }
}
public class Database : DbContext {
public IDbSet<Product> Products { get; set; }
}
public class AuthorizedDatabase {
private readonly Database _database;
private readonly IUserContext _userContext;
public AuthorizedDatabase(Database database, IUserContext userContext) {
_database = database;
_userContext = userContext;
}
private bool Authorize<TEntity>(TEntity entity) {
// Some code here to look at the entity and the _userContext and decide if it should be accessible.
}
public IQueryable<Product> Products {
get {
return _database.Products.Where(Authorize);
}
}
}
This would allow me to cleanly abstract the actual logic around the authorization (and your IUserContext interface can be as simple or complex as required to suite your exact needs.)
To ensure that the user is unable is circumvert this protection using a navigation property (Product.Category.Products, for example.) you might need to turn off lazy loading and explicitly load the required related entities.
Have a look at this post from ADO.NET Team Blog for ideas: loading related entities

Issue with Entity Framework/db relations

I have a class Article:
public class Article
{
public int Id { get; set; }
public string Text { get; set; }
public Title Title { get; set; }
}
And Title:
public class Title
{
public int Id { get; set; }
public string Name { get; set; }
public int MaxChar { get; set; }
}
Before you can write an Article, you have to choose your Title from a list, so your StringLength for Article.Text can be determined. Meaning, this article can only have a certain amount of chars, deppending on what 'Title' the writer has. Example: Title.Name "Title1" can only write an article with 1000 chars (MaxChar), and Title.Name "Title2" can write an article with 3000 chars. So. Thats means the the string length for Article.Text has to come from Title.MaxChar.
The Title entity is prefixed data that will be stored in the db.
Here's what ive done sone far:
The titles from the db are listed in a view, with a link to create action of the ArticleController with a "title" querystring:
#Models.Title
#foreach (var item in Model) {
#Html.ActionLink(item.Name, "Create", "Article", new { title = item.Id}, new FormMethod())
}
You fill the form, and post it. The HttpPost Create action:
[HttpPost]
public ActionResult Create(Article article)
{
if (article.Text.Length > article.Title.MaxChar)
{
ModelState.AddModelError("Text",
string.Format("The text must be less than {0} chars bla bla", article.Title.MaxChar));
}
if (ModelState.IsValid)
{
db.Article.Add(article);
db.SaveChanges();
return RedirectToAction("Index");
}
return View(hb);
}
Here's the issue. The controller also adds a new Title entity. So the next time I navigate to the view where I have to choose Title, there's a duplicate of the last entity I used to write an article.
Should I do this in an entirly new way, or is there a small tweak. Only other thing I can think of, is just sending the MaxChar as a querystring, and have no relations between the models at all. Just seems a bit silly/webforms kindda.
Cheers
UPDATE #1:
Maybe im doing this the wrong way?
Get Create action
public ActionResult Create(int title)
{
var model = new Article
{
Title = db.Title.Find(title)
};
return View(model);
}
Or maybe its in the Model? Like, do I have to set foreign keys? Something like:
[ForeignKey("Title")]
public int MaxChar { get; set; }
public virtual Title Title { get; set; }
But im pretty sure I read some where that it isnt necesary, that EF takes care of that.
Easiest way would probably be to attach the title to the context in your Create action:
// ...
if (ModelState.IsValid)
{
db.Titles.Attach(article.Title);
db.Article.Add(article);
db.SaveChanges();
return RedirectToAction("Index");
}
// ...
Attach tells EF that article.Title already exists in the database, thereby avoiding that a new Title is inserted when you add the article to the context.
You need to have a distinction between your MVC model and your Entities model. Your MVC Article model should look something like this (bear in mind there are some religious debates about what goes into a model):
public class Article
{
public int Id { get; set; }
public string Text { get; set; }
public int TitleID { get; set; }
public IEnumerable<Title> AvailableTitles {get;set;}
}
In your view, you can create a dropdown based off the available titles, and bind it to the TitleID property. The list of available titles would be populated in the parameterless controller method (and the model-bound method as well).
When your model-bound method brings back the TitleID, instantiate the Title object from the Entities framework based off the ID. Create your Entities Article object using that Title object, and save your changes. This should get you where you want to be.

My first View Model

I have been playing around with MVC 3 and looking at populating dropdownlists. I have seen a few examples online that recommend using view models, so here is my first attempt. My code seems to work, but can anybody tell me if this is the correct way to do this?
My model :
public class ContactGP
{
public int TeamID { get; set; }
[Required(ErrorMessage = "Please select a Team Name")]
[DataType(DataType.Text)]
[DisplayName("Team Name")]
public string TeamName { get; set; }
}
My view model :
public class ContactGPViewModel
{
public string SelectedTeamID { get; set; }
public IEnumerable<Team> Teams { get; set; }
}
My controller :
public IEnumerable<Team> PopulateTeamsDropDownList()
{
IEnumerable<Team> lstTeams = _Base.DataRepository.GetTeams();
return lstTeams;
}
public ActionResult ContactGP()
{
var model = new ContactGPViewModel
{
Teams = PopulateTeamsDropDownList()
};
return View(model);
}
And my view :
<p>
#Html.DropDownListFor(
x => x.SelectedTeamID,
new SelectList(Model.Teams, "TeamID", "TeamName")
)
</p>
Your code seems correct. You have defined a view model containing the necessary properties your view will require, filled it up in the controller and passed to this strongly typed view.
I have only a minor remark on the following line inside the PopulateTeamsDropDownList method:
_Base.DataRepository.GetTeams();
I hope you have abstracted this repository with interfaces (or abstract classes) and used DI in order to inject some concrete implementation into your controller. This will weaken the coupling between your controller and the way data is accessed and to simplify unit testing the different layers of your application in isolation.

Resources