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)
Related
Summary:
I'm trying to use two DropDownList controls to filter the data that is currently being sorted and displayed in a view.
What we are going to learn
Creating the ViewController for One to Many and Many-to-Many relationships that could Filter the data using DropDownList
Possible Causes
If my DropdownList code is not terrible wrong, The ViewModel I'm using to display the data has no proper support for the DropDownList items.
In other words, the RazorView and my ViewModels are not compatible for what I'm trying to achieve. If I try to change my ViewModel or RazorView, I get an eldless loop of errors for my existing code.
OR The Linq Query needs an expert attention
Here is FilterViewModel.cs
public IEnumerable <App> Apps { get; set; }
public IEnumerable <Language> Languages { get; set; }
public IEnumerable <Platform> Platforms { get; set; }
public IEnumerable <AgeGroup> AgeGroups { get; set; }
public IEnumerable <ProductCode> ProductCodes { get; set; }
Here is AppsController.cs
public ActionResult FilterApps(App app)
{
var apps = _context.Apps.ToList();
var languages = _context.Languages.ToList();
var productCodes = _context.ProductCodes.ToList();
var platforms = _context.Platforms.ToList();
var ageGroups = _context.AgeGroups.ToList();
var viewModel = new FilterViewModel
{
AgeGroups = ageGroups,
Languages = languages,
Platforms = platforms,
ProductCodes = productCodes,
Apps = apps
.Where(a => a.LanguageId == app.LanguageId && a.PlatformId == app.PlatformId)
// I also tried all possible combinations :(a.Lanage.Id etc)
};
return View("FilterApps", viewModel);
}
Here is the FilterApps.cshtml
#model Marketing.ViewModels.FilterViewModel
<h2>FilterApps</h2>
#using (Html.BeginForm("FilterApps", "Apps", FormMethod.Post, new { enctype = "multipart/form-data" }))
{
<div class="form-group">
#Html.DropDownListFor( m => m.Languages,
new SelectList(Model.Languages, "Id", "Name"),"Select Language",
new { #class = "form-control", #id = "dropDown" })
#Html.DropDownListFor(m => m.Platforms,
new SelectList(Model.Platforms, "Id", "Name"), "Select Platform",
new { #onchange = "this.form.submit();",
#class = "form-control", #id = "dropDown" })
</div>
}
//The existing code below is working fine so far.
#foreach (var group in Model.AgeGroups)
{
<h4>#group.Name</h4>
#foreach (var app in Model.Apps.OrderBy(a => a.AppOrder))
{
if (app.AgeGroupId == group.Id)
{
#app.ProductCode.Name
#app.Name
#app.Platform.Name
}
}
}
Probably unnecessary but I hope the additional information will help.
Additional Information
The App.cs is referencing all other tables e.g.
public Language Language { get; set; }
public int LanguageId { get; set; }
public Platform Platform { get; set; }
public int PlatformId { get; set; }
and so on...
What I have already tried
Several breakpoints and Logs to track the data, I also tried to use the following but it ruins my existing sorting and grouping.
public App App { get; set; } //Instead of the IEnumerable<App>
There are multiple issues with your code.
First you cannot bind a <select> element to a collection of complex objects. A <select> posts back the value of its selected option (which will be an int assuming the Id property of Language is int).
Next the view in the model is FilterViewModel (and your generating form controls with name attributes based on those properties), but your posting back to a different model (App) which does not contain those properties so nothing would bind anyway.
Your adding a null label option ("Select Language") and if that were selected, it would post a null value which would cause your query to fail.
There are also some bad practices which I have noted below.
Your view model should be
public class AppsFilterVM
{
public int? Language { get; set; }
public int? Platform { get; set; }
public IEnumerable<SelectListItem> LanguageOptions { get; set; }
public IEnumerable<SelectListItem> PlatformOptions { get; set; }
...
public IEnumerable <App> Apps { get; set; }
}
Its not clear what AgeGroups and ProductCodes are for so I have omitted them in the code above, and from your comments, I have assumed that the user can filter by either Language or Platform or both
The controller code would be
public ActionResult FilterApps(AppsFilterVM model)
{
var apps = _context.Apps;
if (model.Language.HasValue)
{
apps = apps.Where(x => x.LanguageId == model.Language.Value);
}
if (model.Platform.HasValue)
{
apps = apps.Where(x => x.PlatformId == model.Platform.Value);
}
model.Apps = apps;
ConfigureViewModel(model);
return View(model);
}
private void ConfigureViewModel(AppsFilterVM model)
{
// populate the selectlists
var languages = _context.Languages;
var platforms = _context.Platforms
model.LanguageOptions = new SelectList(languages, "Id", "Name");
model.PlatformOptions = new SelectList(platforms , "Id", "Name");
}
Then in the view (note its making a GET, not a POST)
#model.AppsFilterVM
....
#using (Html.BeginForm("FilterApps", "Apps", FormMethod.Get))
{
#Html.LabelFor(m => m.Language)
#Html.DropdownListFor(m => m.Language, Model.LanguageOptions, "No filter")
#Html.ValidationMessageFor(m => m.Language)
#Html.LabelFor(m => m.Platform)
#Html.DropdownListFor(m => m.Platform, Model.PlatformOptions, "No filter")
#Html.ValidationMessageFor(m => m.Platform)
<input type="submit" value="Filter" />
}
#foreach (var group in Model.AgeGroups)
{
....
There a a few other thing you should not be doing. Your giving both <select> elements the same id attribute which is invalid html (the DropDownListFor() method already generates a unique id based on the property name).
You should not submit a form based on the change event of a <select> Not only is it unexpected behavior, if a user uses the keyboard to navigate through options (e.g. using the arrow keys, or typing a character to go to the first option starting with that letter, then the form will be immediately submitted. In addition, the user might select an option from the 2nd dropdownlist first, which would immediately post before they have a chance to select the option in the first one. Allow the user to make their selections, check them, and then submit the form when they choose to.
Your view should not contain linq queries, and your grouping and ordering should be done in the controller before you pass the model to the view. Your Apps property should in fact be a view model containing a property for the group name, and a collection property for the Apps, (similar to the view models in your previous question) so that the view is simply
#foreach(var group in Model.AgeGroups)
{
#group.Name
foreach (var app in group.Apps)
{
#app.ProductCode
#app.Name
#app.Platform
}
}
You should also consider using ajax to submit your form, which would call separate server method that returns a partial view of just the Apps, and update the DOM in the success callback, which would improve performance. For an example, refer Rendering partial view on button click in ASP.NET MVC.
I am desperately looking for help with customizing the registration page of my project. Based on available documentation, I've updated the identity model, the account view model and all works fine apart from the fact that my dropdownlist for gender or country code is manually hard coded. I can't figure out how to populate the dropdownlists from a database using the entity framework. I hence would highly appreciate if somebody could give me a quick walk through of how this is being achieved. I guess it should be fairly simple, but really been trying for days now.
In order to keep maintenance options simple, I want to go with a table on SQL that feeds into the dropdown. Let's take 'Gender' as example.
I have hence gone ahead and updated the AccountViewModels and added the following at the bottom:
public class GenderList
{
public int ID { get; set; }
public string Gender { get; set; }
}
After that, I went over to the IdentityModels and added the 'public DbSet line, so it looks as follows:
public class ApplicationDbContext : IdentityDbContext<ApplicationUser>
{
public DbSet<GenderList> GenderLists { get; set; }
public ApplicationDbContext()
: base("DefaultConnection", throwIfV1Schema: false)
{
}
public static ApplicationDbContext Create()
{
return new ApplicationDbContext();
}
}
And that's where I think I get lost... it's obvious that I have to update the AccountController as well (which I have done...) but I'm not quite sure how to best approach it, as the existing Register.cshtml would not allow me to load multiple models, etc.
For what concerns the controller, I've tried (and failed with...) the following:
public ActionResult GetGender()
{
GenderList db = new GenderList();
ViewBag.Gender = new SelectList(db.Gender, "ID", "Gender");
return View();
}
And last but not least, the relevant section of my Register.cshtml:
#Html.LabelFor(m => m.Gender, new { #id = "registerGenderLabel", #class = "registerLabel col-xs-offset-1 col-xs-10 col-sm-offset-1 col-sm-10 col-md-offset-1 col-md-3 col-lg-offset-1 col-lg-4" })
#*#{ var gender = new List<SelectListItem>
{
new SelectListItem{ Text = "female", Value = "female" },
new SelectListItem { Text = "male", Value = "male"}
};
}*#
#Html.DropDownListFor("Gender","Select")
I'm not sure I entirely understand what you are asking. But for select lists, just add a SelectListItem as a Property of the Model that is the main model for the View.
In model.
public <List>SelectListItems AddressPurposes {get; set;}
public string AddressPurpose {get; set;}
Populate this in the controller as use the DropDownListFor as such, replacing the strings with the values from your db in a loop. Here I'm just adding a hard coded value but you can obviously substitute whatever:
model.AddressPurposes = new List<SelectListItem>() { };
model.AddressPurposes.Add(new SelectListItem() { Text = "New Address", Value = "New Address" });
In view:
#Html.DropDownListFor(m => m.AddressPurpose, Model.AddressPurposes, new { #class = "input-sm" })
I know there are a lot of similar question here but none seem quite the same as mine.
In my View:
#model LocalInformedMotionServer.Models.FeedData
#Html.DropDownList("Premise", Model.LoadUp("key"), new { #style = "width: 218px;height:35px;" })
In my controller:
public class CloudController : Controller
{
public IEnumerable<wsCommon.Premise> Premises { get; set; }
public ActionResult Feed(string key)
{
var feedData = new FeedData();
Premises= feedData.LoadUp(key);
return View(feedData);
}
}
In my Model:
public class FeedData
{
public IEnumerable<wsCommon.Premise> LoadUp(string saltKey)
{
Premises premises = new InformedBiz.Premises();
return premises.GetPremises(saltKey);
}
}
It errors because the variable:
"key"
in this call:
Model.LoadUp("key")
is being read in as'null' in my controller method.
Of course as this is all new to me I could be doing this all wrong..
ADDITIONAL:
In my CloudController Class I have this:
public class CloudController : Controller
{
public ActionResult Feed(string saltKey)
{
var feedData = new FeedData();
feedData.LoadUp(saltKey);
return View(feedData);
}
public ActionResult Index()
{
return View();
}
public ActionResult LogIn()
{
return View();
}
}
I'm not sure what your Premise class looks like, but I usually use an IEnumberable of SelectListItem for drop downs in my views. So you could do something like this:
public IEnumerable<SelectListItem> LoadUp(string saltKey)
{
Premises premises = new InformedBiz.Premises();
return premises.GetPremises(saltKey).Select(
p => new SelectListItem { Text = p.Name, Value = z.PremiseId.ToString() }
);
}
You'll also need to create a Post ActionResult method that accepts the model in your view (FeedData) as well as wrap your DropDownList control in a Html.BeginForm, to post results to the controller. Hope this makes a bit of sense.
You have not posted the properties of your FeedData model but assuming it contains a property which is typeof Premise and you want to be able to select a Premise from a collection, then using a view model that represents what you want to display/edit is the recommended approach (refer View Model Design And Use In Razor Views and What is ViewModel in MVC?)
You view model might look like
public class FeedDataVM
{
.....
[Display(Name = "Premise")]
[Required(ErrorMessage = "Please select a Premise")]
public int? SelectedPremise { get; set; }
....
public SelectList PremiseList { get; set; }
}
and in your controller (not sure what saltKey is for?)
public ActionResult Feed(string saltKey)
{
FeedDataVM model = new FeedDataVM();
IEnumerable<Premise> premises = // get the collection of premise objects from your repository
// assuming you want to display the name property of premise, but post back the key property
model.PremiseList = new SelectList(premises, "key", "name");
return View(model);
}
View
#model FeedDataVM
#using(Html.BeginForm())
{
....
#Html.LabelFor(m => m.SelectedPremise)
#Html.DropDownListFor(m => m.SelectedPremise, Model.PremiseList, "-Please select")
#Html.ValidationMessageFor(m => m.SelectedPremise)
....
<input type="submit" value="Save" />
}
and the POST method
public ActionResult Feed(FeedDataVM model)
{
// model.SelectedPremise contains the value of the selected option as defined by the key property of Premise
}
Side note: Your FeedData model contains a method to retrieve a collection of Premise which appears to be calling another service. You should avoid this type of design which makes it difficult to debug and unit test. Your controller is responsible for initializing/getting you data models and view models and for populating/mapping their properties.
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 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.