I am new to C# and MVC. I have read a few books and spent time on this site reading Q&A on various topics. I have a test project up and running to get the basics down that queries out a list of Groups and returns them to a simple View. I like the idea of the Repository pattern and have taken a stab at implementing that. A few notes... For now, I am not using EF or Linq2Sql but maybe in the future. I am not sure if I will keep my using statements in the GroupRepository or add some Dispose statements via a Try/Catch/Finally as I need a way to manage exceptions anyway.
Just wanted some advice/critique on my current setup as I continue learning.
Base.cs
namespace Test.Models
{
public class Base : IDisposable
{
protected string Connection
{
get
{
return System.Configuration.ConfigurationManager.ConnectionStrings["TestDB"].ConnectionString;
}
}
}
}
Group.cs
namespace Test.Models
{
public class Group
{
public int ID { get; set; }
public string Name { get; set; }
public bool IsActive { get; set; }
public DateTime Created { get; set; }
}
}
GroupRepository.cs
namespace Test.Models
{
public class GroupRepository : Base, IGroupRepository
{
public List<Group> GetAllGroups()
{
List<Group> groups = new List<Group>();
SqlDataReader reader;
using (SqlConnection conn = new SqlConnection(Connection))
using (SqlCommand cmd = new SqlCommand("GetAllGroups", conn))
{
cmd.CommandType = System.Data.CommandType.StoredProcedure;
conn.Open();
reader = cmd.ExecuteReader();
while (reader.Read())
{
Group group = new Group();
group.ID = reader.GetInt32(0);
group.Name = reader.GetString(1);
group.IsActive = reader.GetBoolean(2);
group.Created = reader.GetDateTime(3);
groups.Add(group);
}
}
return groups;
}
}
}
IGroupRepository.cs
namespace Test.Models
{
public interface IGroupRepository
{
List<Group> GetAllGroups();
}
}
GroupController
namespace Test.Controllers
{
public class GroupController : Controller
{
private IGroupRepository _repository;
public GroupController() : this(new GroupRepository())
{
}
public GroupController(IGroupRepository repository)
{
_repository = repository;
}
public ActionResult Index()
{
return View(_repository.GetAllGroups());
}
}
}
View
#model IEnumerable<Test.Models.Group>
#{
ViewBag.Title = "Group List";
}
<table>
#foreach (var item in Model) {
<tr>
<td>
#item.ID
</td>
<td>
#item.Name
</td>
<td>
#item.IsActive
</td>
<td>
#item.Created
</td>
</tr>
}
</table>
Thanks All!
For basic projects / learning this if fine. Once you start getting into more advanced scenarios you'll find that the data you display in your views will look less like how it's stored in the database and you may want to have models which are specific to your views rather than returning the models directly from your repository into your views.
This is something you can tweak as you go however and if you're just doing basic operations then that's fine.
Another thing that tends to grow redundant quickly is having specific data access methods on your repository. Generally what I try and do is construct a query object and pass that into the repository, which executes the query and then returns the results. This tends to help me avoid redefining the IRepository contract every time I want to get a new set of data, or query by a different set of parameters.
Apart from that everything looks pretty standard and the main concept of separating data access from the controllers via the repository is solid.
Here's a (very basic) sample implementation of a query object pattern (this is really just to give you an idea, you will want to clean it up before you start using it):
public class GroupQueryParams
{
public int? GroupId {get;set;}
public string GroupName {get;set;}
}
public class GroupRepository : Base, IGroupRepository
{
public List<Group> GetGroups(GroupQueryParams query)
{
var storedProcName = "GetAllGroups";
if(query.GroupId.HasValue)
storedProcName = "GetGroupById";
if(!string.IsNullOrEmpty(query.GroupName))
storedProcName = "GetGroupByName";
//setup a parameter collection here to pass into the command
List<Group> groups = new List<Group>();
SqlDataReader reader;
using (SqlConnection conn = new SqlConnection(Connection))
using (SqlCommand cmd = new SqlCommand(storedProcName, conn))
{
cmd.CommandType = System.Data.CommandType.StoredProcedure;
conn.Open();
reader = cmd.ExecuteReader();
while (reader.Read())
{
Group group = new Group();
group.ID = reader.GetInt32(0);
group.Name = reader.GetString(1);
group.IsActive = reader.GetBoolean(2);
group.Created = reader.GetDateTime(3);
groups.Add(group);
}
}
return groups;
}
}
This way you've got a single method "GetGroups" and you can change the query params and the underlying logic as much as you want but don't have to keep redefining the interface every time you want to add a new query.
Related
var listdata = db.UserDetails.Select(m => new SelectListItem
{
Value = m.userid.ToString(),
Text = string.Format("{0}{1}{2}{3}",m.bankname,m.userid,m.gender,m.name)
});
Here UserDetails is the table that is present in the database and this is the way i am trying to display every entry of the table.
Controller
[HttpGet]
public ActionResult getAll()
{
var listdata = db.UserDetails.Select(m => new SelectListItem
{
Value = m.userid.ToString(),
Text = string.Format("{0}{1}{2}{3}",m.bankname,m.userid,m.gender,m.name)
});
return View("getAll", listdata);
}
View
#model UserApp.Models.UserDetails
#{
ViewBag.Title = "getAll";
}
<h2>getAll</h2>
#foreach (var item in Model)
{
<tr>
<td>
#Html.DisplayFor(modelItem => item.name)
</td>
<td>
#Html.DisplayFor(modelItem => item.gender)
</td>
</tr>
}
Model
namespace UserApp.Models
{
public class UserModel : IEnumerable<UserModel>
{
public int userid {get; set;}
public string name{get; set;}
public IList<SelectListItem> bankname { get; set; }
public string gender{get; set;}
}
}
How do i get the elements and display them properly on the view?
I can't seem to get a proper solution.
Stuck on this thing for hours.
P.s: new to it, any help will be appreciated.
First, add ToList() for your listdata to make it list, currently it is still IQueryable , second your view accepts model, you are passing list of model, I guess you want that to be list not model, something like this
#model List<UserApp.Models.UserDetails>
Third, you are selecting SelectListItem but you are using UserApp.Models.UserDetails, I think you should be doing something like this
var listdata = db.UserDetails.ToList().Select(x => new UserApp.Models.UserDetails {
userid = x.userid, (repeat the same for all)
}).ToList();
because looking at your code you don't need selectListItem, you need UserApp.Models.UserDetails.
That should fix all your problems, I hope I didn't miss any.
My approach may not be the best approach but it seems to work for me.
I usually have my model for the item :
model :
namespace UserApp.Models
{
public class UserModel
{
public int userid {get; set;}
public string name{get; set;}
public IList<SelectListItem> bankname { get; set; }
public string gender{get; set;}
}
}
Then I have in my database class ( a class that calls the database and populates the queries etc: Call it CodeDB() for this example)
DB getter :
public List<UserModel> getUsers(){
{
List<UserModel> myUsers = new List<userModel>();
// however you are accessing your db do it here
string sql = "select * ...";
//access DB
//open connection
//run query command usually for me it is rdr = cmd.ExecuteReader();
while(rdr.Read()){
UserModel retrievedUser = new UserModel();
retrievedUser.userid = (int)rdr[0];
retrievedUser.name = rdr[1].ToString();
... add the other fields
myUsers.Add(retrievedUser);
}
//close db connection
return myUsers
}
In my Controller
//call my database class
CodeDB() DB = new CodeDB()
[HttpGet]
public ActionResult getAll()
{
List<UserModel> viewUsers = DB.getUsers();
ViewBag.users = viewUsers
return View();
}
in the view
#{
if(Viewbag.users != null)
{
foreach(UserApp.Models.UserModel u in ViewBag.users)
{
#Html.Raw( " userID : " + u.userid +" Gender : " + u.gender)
}
}
}
I think you could do. MVC Scaffolding of Crud with there Views Auto Generated
When you make your controller There's an option "MVC Controller with Views"
Then it will ask For your Model that you want to use for scaffolding which will be
"UserModel" Then just give your Controller a Name.
Now if you look at the Index View of your Controller it will have all the attributes you want and don't want.But of course, you can remove the unnecessary attributes
Hope this helps!
I am completely new to MVC and EF ( coming from web forms ) so I apologize up front if this question is dumb ;)
I need to build an app that a user logs into using AspIdentity that also ties to a Community table. Once logged in they can only see data that is community specific site wide. I have the app built but I am struggling with how it will only pull data based on a Community ID... In web forms I would build all this logic myself :/
Basically there are the built in Identity tables and the ones I defined ( Community, Homes, Residents etc. )
Seems like a simple app and it is but I dont see any tutorials that do this.
Thanks!
You can approach it from two ways. If the user only ever belongs to one community, then you can simply use the user's community to restrict your queries. Essentially, you'll do something like:
var foo = db.Foos.SingleOrDefault(m => m.Id == id && m.CommunityId = user.CommunityId);
if (foo == null)
{
return new HttpNotFoundResponse();
}
In other words, you just add an additional condition when pulling items from your database to match the user's community. That way, the user will only ever see something other than a 404 if it belongs to their community.
Option 2 is to make the community part of the URL, and use that to restrict your queries similar to the above. For example, you might have something like /awesome-community/foo. Then, in the action that serves that you'd have something like:
public ActionResult Foo(string community)
{
...
And, again, you'd just limit the query by that community as above. If you follow this approach, though, you will probably then need to also do something like create a custom Authorize attribute that verifies that your user actually belongs to the community.
Here's how I would tackle it, and see if it at least gets you rolling. I'm taking a clean MVC web project that comes with Visual Studio:
I'll also be borrowing the community concept from StackOverflow where you have types of visitors, and content that is applicable. (In the example's case, "Programmers" have their set of questions, while "Designers" have their set.)
An finally, for brevity, I've tried to exclude portion of the files I'm not modifying. To keep things short, existing code has been replaced with a Snip comment.
Onward!
Infrastructure
Setup the additional entities that give us a Community and data specific to that community (Question).
public class Community
{
public int Id { get; set; }
public string Name { get; set; }
}
public class Question
{
public int Id { get; set; }
public int CommunityId { get; set; }
public virtual Community Community { get; set; }
public string Title { get; set; }
}
Add these entities to the ApplicationUser as well as to the ApplicationDbContext.
~/Models/IdentityModels.cs:
public class ApplicationUser : IdentityUser
{
public int CommunityId { get; set; }
public virtual Community Community { get; set; }
/* Snip */
}
public class ApplicationDbContext : IdentityDbContext<ApplicationUser>
{
public DbSet<Community> Communities { get; set; }
public DbSet<Question> Questions { get; set; }
/* Snip */
}
Attach the community to the user. Claims make this really easy since we're just attaching additional information to the user profile. Furthermore, the Visual Studio folks already created a placeholder for us.
// This makes it easier to reference and makes sure we're consistent
public static class CustomClaimTypes
{
public const string CommunityId = "http://stackoverflow.com/claims/communityid";
}
public class ApplicationUser : IdentityUser
{
public int CommunityId { get; set; }
public virtual Community Community { get; set; }
public async Task<ClaimsIdentity> GenerateUserIdentityAsync(UserManager<ApplicationUser> manager)
{
// Note the authenticationType must match the one defined in CookieAuthenticationOptions.AuthenticationType
var userIdentity = await manager.CreateIdentityAsync(this, DefaultAuthenticationTypes.ApplicationCookie);
// Add custom user claims here
userIdentity.AddClaim(new Claim(CustomClaimTypes.CommnunityId, this.CommunityId.ToString()));
return userIdentity;
}
}
Note that we've added userIdentity.AddClaim(...) (and our static class).
Presentation
Modify the view model to accommodate our change.
~/Models/AccountViewModels.cs:
public class RegisterViewModel
{
/* snip */
[Required]
[Display(Name = "Community")]
public int CommunityId { get; set; }
}
Plumb this into our Controller.
~/Controllers/AccountController.cs:
[AllowAnonymous]
public ActionResult Register()
{
ViewBag.Communities = GetCommunities();
return View();
}
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<ActionResult> Register(RegisterViewModel model)
{
if (ModelState.IsValid)
{
/* Snip */
}
ViewBag.Communities = GetCommunities();
return View(model);
}
// The additional method we're calling above:
private IEnumerable<SelectListItem> GetCommunities()
{
var db = ApplicationDbContext.Create();
var communities = db.Communities.ToDictionary(k => k.Id, v => v.Name);
return communities.Select(c => new SelectListItem
{
Text = c.Value,
Value = c.Key.ToString()
});
}
Flush it out through our view.
~/Views/Account/Register.cshtml:
#* Snip *#
<div class="form-group">
#Html.LabelFor(m => m.CommunityId, new { #class = "col-md-2 control-label" })
<div class="col-md-10">
#Html.DropDownListFor(m => m.CommunityId, (IEnumerable<SelectListItem>)ViewBag.Communities, new { #class = "form-control" })
</div>
</div>
#* Snip *#
Create a new action the Home controller:
[Authorize]
public ActionResult Questions()
{
var communityId = GetCommunityId();
var db = ApplicationDbContext.Create();
var community = db.Communities.SingleOrDefault(c => c.Id == communityId);
var questions = db.Questions.Where(c => c.CommunityId == communityId).AsEnumerable();
ViewBag.Message = community.Name;
return View(questions);
}
// Helper that's probably better suited in a more common location
private Int32 GetCommunityId()
{
var communityIdClaim = ((ClaimsPrincipal)User).Claims
.FirstOrDefault(c => c.Type == CustomClaimTypes.CommnunityId);
Int32 communityId;
return communityIdClaim != null && Int32.TryParse(communityIdClaim.Value, out communityId)
? communityId
: -1; // Erroneous id
}
And a view to go with it:
#model IEnumerable<#* Your.Model.Namespace. *#Question>
#{
ViewBag.Title = "Questions";
}
<h2>#ViewBag.Title</h2>
<h3>#ViewBag.Message</h3>
<table class="table">
<thead>
<tr>
<td>Question</td>
</tr>
</thead>
<tbody>
#foreach (var question in Model)
{
<tr>
<td>#question.Title</td>
</tr>
}
</tbody>
</table>
Add a link to your menu.
`~/Views/Shared/_Layout.cshtml
#* Snip *#
<ul class="nav navbar-nav">
#* Snip *#
<li>#Html.ActionLink("Questions", "Questions", "Home")</li>
</ul>
#* Snip *#
Sample Content
Create a very basic DbInitializer that will populate some info we can use. So, back to the ~/ModelsIdentityModels.cs file:
public class ApplicationDbInitializer : CreateDatabaseIfNotExists<ApplicationDbContext>
{
protected override void Seed(ApplicationDbContext context)
{
var communities = new[]{
new Community { Name = "Programmers" },
new Community { Name = "Designers" }
};
var questions = new[]{
new Question { Community = communities[0], Title = "How do I write my first application?" },
new Question { Community = communities[0], Title = "What is unit testing?" },
new Question { Community = communities[0], Title = "Inversion of Control: What is it?" },
new Question { Community = communities[1], Title = "What is a StyleSheet?" },
new Question { Community = communities[1], Title = "HTML5: What do I need to know?" },
new Question { Community = communities[1], Title = "Is Silverlight still being used?" },
};
context.Communities.AddRange(communities);
context.Questions.AddRange(questions);
context.SaveChanges();
}
}
public class ApplicationDbContext : IdentityDbContext<ApplicationUser>
{
// for simplicity, let's add a static ctor within the context
static ApplicationDbContext()
{
Database.SetInitializer(new ApplicationDbInitializer());
}
/* Snip */
}
Give it a run-through
Now you should be able to register a user, and see content that's applicable to the community they're a part of:
Paul (the Programmer)
Debby (the Designer)
I am using C#, MVC3, EF5, SQL Server 2008 R2.
I have an intersection table ie
Lecturer -< LecturerCourse >- Course
The list of Lecturers are populated.
When I add a course, it would be neat to have a list of Lecturers that I could select from, that teach the course in question. When I save the new Course record, this multiselect also should save its data back to the "LecturerCourse" table via Model Binding.
I am using EF5.
Can you recommended a simple and standard approach to solving CRUD for a join, ie "LecturerCourse", table? I have looked online, but some of the approaches seem very complicated.
Many thanks.
Alright, it's going to be a long one. To allow this to happen in "one page" (through POST, or you could use Ajax, technically), you need a combination of a Get and Post version of the method and to construct your view model correctly. Below are the classes that I will use for demonstration purposes:
public class NewCourse
{
[Required]
public string Name { get; set; }
// And your other properties
public int[] LecturerIds { get; set; }
}
public class ViewLecturer
{
public int Id { get; set; }
public int Name { get; set; }
}
public class NewCourseViewModel
{
public NewCourse Course { get; set; }
public IEnumerable<ViewLecturer> Lecturers { get; set; }
}
NewCourseViewModel will be the model for the View (see below). ViewLecturer will give you a lighter mapping between your available Lecturer and the information required to Add to them.
As for the Controller:
public class CourseController : Controller, IDisposable
{
private Lazy<YourContext> lazyContext =
new Lazy<YourContext>(() => new YourContext());
private YourContext Context
{
get { return lazyContext.Value; }
}
public ActionResult New()
{
var model = new NewCourseViewModel {
Course = new NewCourse(),
Lecturers = Context.Lecturers
.Select(l => new ViewLecturer { Id = l.Id, Name = l.Name })
};
return View(model);
}
[HttpPost]
public ActionResult New(NewCourse course)
{
if(ModelState.IsValid)
{
var lecturers = course.Lecturers
.Select(l => new Lecturer { Id = l.Id })
.ToList();
foreach(var lecturer in lecturers)
Context.Lecturers.Attach(lecturer);
var newCourse = new Course {
Name = course.Name,
// ... and the rest of the mapping
Lecturer = lecturers
};
context.Courses.Add(newCourse);
context.SaveChanges();
// Could have to handle DbUpdateException if you want
return RedirectToAction(...);
}
return View(new NewCourseViewModel {
Course = course,
Lecturers = Context.Lecturers
.Select(l => new ViewLecturer { Id = l.Id, Name = l.Name })
});
}
public void Dispose()
{
if(lazyContext.IsValueCreated)
lazyContext.Value.Dispose();
}
}
Your first New method will give you the entry point for your Course creation page. The rest of the validation and actual adding will be done through the [HttpPost]overload. As for your View (that should be in the ~/Views/Course/New.cshtml):
#model NewCourseViewModel
// ... Then when you are ready to begin the form
#using(Html.BeginForm("New", "Course", FormMethod.Post))
{
// Your List of Lecturers
#Html.ListBoxFor(m => m.Course.LecturerIds,
new MultiSelectList(
Model.Lecturers,
"Id",
"Name",
m.Course.LecturerIds ?? new int[0]
))
// Your Other Model binding
}
When the submit button will be pressed, the action matched will be the New(NewCourse course). The names are important because of the way the HtmlHelpers generate their Ids. Because we are only included one property of the whole view model, it will match the parameter name course based on the view model's Course property. You will get a list of Ids for the Lecturers which you will be able to use to attach to the DbContext and add directly to the new Course model (Entity Framework will do the rest). In cases where there was a problem, we can get back the list of lecturers and re-use the same NewCourse in the view model.
Now this is example is very basic but it should give you a good starting point as to how you can structure your view model.
I just followed an MVC tutorial on creating an image Gallery, which connects the Controller to the data connection, like this:
ImageController.cs:
...
private CustomMembershipDB db = new CustomMembershipDB();
public ViewResult Index()
{
return View(db.lm_pics.ToList());
}
...
Instead of connecting directly to CustomMembershipDB, I'd like to use my own Model named GalleryModel.cs. I'm thinking this would allow me to create more functionality that just direct data access.
I am not sure how to write this model, or how to reference it in the controller so that it behaves the same way as a direct database connection does now.
Currently, my GalleryModel.cs file looks lke this (edited to correct error):
namespace LMProj_MVC.Models
{
public class GalleryModel
{
public string Picname { get; set; }
public string Decription{ get; set; }
public int Userid { get; set; }
}
public class PicDBContext : CustomMembershipDB
{
public DbSet<GalleryModel> GalleryModel { get; set; }
}
}
I'd like to be able to show the gallery using an iEnumberable list as I am doing now, in addition to creating other methods. Could someone tell me what I'm missing?
You need to create an instance of your model object for each of your database pictures. You could use LINQ to do this, for example:
var picSummaries = db.lm_pics.Select(pic => new GalleryModel{
Picname = pic.Name,
Description = pic.Description,
Userid = pic.User.Id
});
Or you could use a for each loop:
var picSummaries = new List<GalleryModel>();
foreach (var pic in db.lm_pics)
{
picSummaries.Add(new GalleryModel{
Picname = pic.Name,
Description = pic.Description,
Userid = pic.User.Id
});
}
then return the view as before:
return View(picSummaries);
I'm kind of new to razor MVC, and I'm wondering how can I read the values I return in the view?
My code is like this:
public ActionResult Subject(int Category)
{
var db = new KnowledgeDBEntities();
var category = db.categories.Single(c => c.category_id == Category).name;
var items = from i in db.category_items
where i.category_id == Category
select new { ID = i.category_id, Name = i.name };
var entries = from e in db.item_entry
where items.Any(item => item.ID == e.category_item_id)
select new { ID = e.category_item_id, e.title };
db.Dispose();
var model = new { Name = category, Items = items, Entries = entries };
return View(model);
}
Basically, I return an anonymous type, what code do I have to write to read the values of the anonymous type in my view?
And if this is not possible, what would be the appropriate alternative?
Basically, I return an anonymous type
Nope. Ain't gonna work. Anonymous types are emitted as internal by the compiler and since ASP.NET compiles your views into separate assemblies at runtime they cannot access those anonymous types which live in the assembly that has defined them.
In a properly designed ASP.NET MVC application you work with view models. So you start by defining some:
public class MyViewModel
{
public string CategoryName { get; set; }
public IEnumerable<ItemViewModel> Items { get; set; }
public IEnumerable<EntryViewModel> Entries { get; set; }
}
public class ItemViewModel
{
public int ID { get; set; }
public string Name { get; set; }
}
public class EntryViewModel
{
public int ID { get; set; }
public string Title { get; set; }
}
and then you adapt your controller action to pass this view model to the view:
public ActionResult Subject(int Category)
{
using (var db = new KnowledgeDBEntities())
{
var category = db.categories.Single(c => c.category_id == Category).name;
var items =
from i in db.category_items
where i.category_id == Category
select new ItemViewModel
{
ID = i.category_id,
Name = i.name
};
var entries =
from e in db.item_entry
where items.Any(item => item.ID == e.category_item_id)
select new EntryViewModel
{
ID = e.category_item_id,
Title = e.title
};
var model = new MyViewModel
{
CategoryName = category,
Items = items.ToList(), // be eager
Entries = entries.ToList() // be eager
};
return View(model);
}
}
and finally you strongly type your view to the view model you have defined:
#model MyViewModel
#Model.Name
<h2>Items:</h2>
#foreach (var item in Model.Items)
{
<div>#item.Name</div>
}
<h2>Entries:</h2>
#foreach (var entry in Model.Entries)
{
<div>#entry.Title</div>
}
By the way to ease the mapping between your domain models and view models I would recommend you checking out AutoMapper.
Oh, and since writing foreach loops in a view is kinda ugly and not reusable I would recommend you using display/editor templates which would basically make you view look like this:
#model MyViewModel
#Model.Name
<h2>Items:</h2>
#Html.DisplayFor(x => x.Items)
<h2>Entries:</h2>
#Html.DisplayFor(x => x.Entries)
and then you would define the respective display templates which will be automatically rendered for each element of the respective collections:
~/Views/Shared/DisplayTemplates/ItemViewModel:
#model ItemViewModel
<div>#item.Name</div>
and ~/Views/Shared/DisplayTemplates/EntryViewModel:
#model EntryViewModel
<div>#item.Title</div>