Display error even while Model.IsValid returns true - asp.net

I'm making a website that pulls some information from a SQL DB.
I have made a Model for validating that i have the information i need before i try the query. This means (if things go right) the method will always resolve and the Model.IsValid will be true. The query is to get all rows between two given dates.
Now comes my problem - When it gets into the ActionResult where it calls the query i first want to check if the amount of rows returned is less than 1000.
If it is < 1000 i want it to show the view with the rows, however if it's greater than 1000 i want an error message to be displayed that tells the user to narrow the search criterias.
I might be going about this the complete wrong way, and my initial idea was to "simply" change the validator to no longer being valid. Which would the automatically trigger the error message from the model. After searching around though that doesn't seem to be a possibility.
Anyone got some ideas how to go about this? Am completely wrong about my approach?
ValidatorModel:
public class DateValidator
{
[Required]
public DateTime StartDateTime { get; set; }
[Required]
public DateTime EndDateTime { get; set; }
}
ActionResult:
public ActionResult GetListFromDateRange(DateValidator validator)
{
RangeParent parent = new RangeParent();
if (ModelState.IsValid)
{
int queryCount =
repository.GetCountFromDateRange(validator.StartDateTime,
validator.EndDateTime);
if (queryCount < 1000)
{
parent.Meters =
repository.GetListFromDateRange(validator.StartDateTime,
validator.EndDateTime);
return View(Constants.ViewNames.DisplayRangeData, parent);
}
else
{
//display error message telling user to narrow the datetime range
return new EmptyResult();
}
}
return View(Constants.ViewNames.IndexView);
}
And last the two queries i currently use:
public List<Meter> GetListFromDateRange(DateTime startDateTime, DateTime endDateTime)
{
using (dbContext = new HeatDataVerifEntities())
{
return dbContext.Meters.Where(p => p.Created >= startDateTime && p.Created <= endDateTime).OrderBy(p => p.Created).ToList();
}
}
public int GetCountFromDateRange(DateTime startDateTime, DateTime endDateTime)
{
using (dbContext = new HeatDataVerifEntities())
{
return dbContext.Meters.Where(p => p.Created >= startDateTime && p.Created <= endDateTime).Count();
}
}
Another thing i considered was to create another ActionResult that runs the check for the row size which i would call instead of 'GetListFromDateRange' - and then have that either return an empty partial view along with calling the method for getting the list of item, or returning a partial view that has an error message in it. It just seemed like the should/could be an easier or smarter solution to that.
Again i have been searching around, but as i also mentioned i might simply just be looking at this wrong.
Thanks in advance.

Displaying around 1000 records on one page is not exactly useful, plus it affects many things, such as the performance of your database, depending on how many users request data at the same time, how fast the website displays the data to the users and ultimately your website might crash completely if too many sets of data are requested at the same time.
Instead, why don't you simply add pagination? Decide on a page size, say 20, return those records from your query, together with the number of pages required to display all data. You can then use that to build the page pagination itself. So you never return more than 20 rows from your db, the pressure is minimal, the site runs quickly.
The way you are going now, you are requesting a lot of data, and if you have more than 1000 records all you do is display an error message expecting the user to make another request. You already have the data though, but you're not using it for anything other than a count. Unless a course, all you do is run a count first and then if less than 1000 then you run the actual query.
Model validation is run on inputs, not outputs and I wouldn't suggest you try to change the way MVC works, just to fit a specific scenario which shouldn't happen anyway.
If you want to crack on with your idea then run the count query first, if result is over 1000 then return an error message and display that to the user. You can do this without any kind of custom, non-model validation.

You can always add a custom error message that will show up in ValidationSummary
ModelState.AddModelError(string.Empty, "My own error message");

Related

How do I get data from a related user in a subclass of ParseObject?

I'm working in VisualStudio on a Xamarin project.
I have a ParseObject subclass. It has a field "SentBy" that links to a ParseUser. It's constructed like this:
[ParseClassName("Beacon")]
public class Stuff_ParseBeacon : ParseObject
{
public Stuff_ParseBeacon() { }
[ParseFieldName("sentBy")]
public ParseUser SentBy
{
get { return GetProperty<ParseUser>(); }
set { SetProperty(value);}
}
}
I'm trying to include a parameter that doesn't need to be in the ParseClass on the server, which gets the data stored under "phoneNumber" from the linked user. So, like this:
public string SentByPhoneNumber
{
get
{
return SentBy.Get<string>("phoneNumber");
}
}
But I keep getting the error that there's no such key in that ParseUser--which is false, because all my ParseUsers store a phone number.
What am I doing wrong?
(BTW, in case it matters: I'm trying to use SentByPhoneNumber as a bindable property)
I don't think this is the best way to do it, but this is what I ended up doing.
I just added a phoneNumber field to the Beacon class, and it gets directly filled by the user who creates the Beacon--the user who gets stored in the SentBy field.
The problem with this is that the won't automatically update if I change the phoneNumber on the SentBy user.
This is pretty much exactly what I didn't want to do in my question.
So I'm sure there's a better way to refer to a field on a stored user than manually putting that data in the referring class at the time of creation, but it's what I've had to do to move on.

Is there a better way to implement role based access in ASP.NET framework?

Basically I've spent the last few days trying to figure out how to add simple Admin and Member roles onto a website I'm developing for a friend. (I am using ASP.NET Framework 5.2.7.0). I know that Microsoft has a nice role based access feature built in which allows you to put something like [Authorize Role=("Admin") at the top of the controller; however I have not been able to get it to work at all and most of the resources I've found are for ASP.NET Core.
I've tried modifying my web.config file to enable the role based access (and hopefully migrate the roles and such to my database). But since I've been unable to figure any of this out, I've tried going a more hacky route. (**I am not an advanced programmer, I've been doing this for about a year now and am in no way a pro). This is what I've basically come up with in my attempt to verify if a user is an admin (which also didn't work).
[Authorize]
public class AdminController : Controller
{
private LDSXpressContext db = new LDSXpressContext();
public ActionResult AdminPortal()
{
IsAdmin();
return View();
}
private ActionResult IsAdmin()
{
string name = User.Identity.Name;
//The User.Identity.Name stores the user email when logged in
var currentUserObject = db.accounts.Where(x => x.clientEmail == name);
Account currentUser = new Account();
foreach (var user in currentUserObject)
{
//I loop through the results, even though only one user should
//be stored in the var CurrentUserObject because it's the only
//way I know how to assign it to an object and get its values.
currentUser = user;
}
if (currentUser.role == 2) //the number 2 indicates admin in my db
{
return null;
}
else
{
//Even when this is hit, it just goes back and returns the
//AdminPortal view
return RedirectToAction("Index", "Home");
}
}
}
Now I'm nearly positive that is is NOT a very secure way to check if a signed in user is an admin, but I was hoping that it would at least work. My idea was when someone attempted to access the AdminPortal, the IsAdmin method would run and check if the user is an admin in the database. If they are, then it returns null and the AdminPortal view is displayed, if they are not an Admin, then they are redirected to the Index view on the home page. However, the AdminPortal page is always displayed to any user and this doesn't seem to work either. I've even stepped into the code and watched it run over the return RedirectToAction("Index", "Home"); action, but then it jumps back to the AdminPortal method and just returns the AdminPortal view. So my question is:
1) If anyone happens to have experience with Role Based access in ASP.NET Framework, I would love some tips on how to get it set up
or,
2) If all else fails and I need to use my hacky method, why does it continue to return the AdminView even when the user is not an admin.
**Note: I know I could create a function that returns true or false if the user is an Admin or not, and then have an if/else statement in the AdminPortal controller that will return on view for true and another for false, however I don't want to have to implement that onto every ActionMethod, it'd be nice to keep it down to one line, or just the [Authorize Role="Admin] above the controller if possible.
Thank you guys so much for any help provided, I've been trying to research and fix this for days now and decided to reach out and ask the community!
At a minimum, you'll want to make some adjustments to what you're doing:
[Authorize]
public class AdminController : Controller
{
public ActionResult AdminPortal()
{
if(IsAdmin())
{
return View();
}
return RedirectToAction("Index", "Home");
}
private bool IsAdmin()
{
bool isAdmin = false;
using(LDSXpressContext db = new LDSXpressContext())
{
string name = User.Identity.Name;
//The User.Identity.Name stores the user email when logged in
// #see https://learn.microsoft.com/en-us/dotnet/api/system.linq.enumerable.singleordefault
var currentUser = db.accounts.SingleOrDefault(x => x.clientEmail.Equals(name, StringComparison.OrdinalIgnoreCase));
// If the email doesn't match a user, currentUser will be null
if (currentUser != null)
{
//the number 2 indicates admin in my db
isAdmin = currentUser.role == 2;
}
}
return isAdmin;
}
}
First off, DbContext instances are meant to used, at most, per the lifetime of an HTTP request. Moving it from the class / controller level and placing it within a using block makes sure that it's properly disposed.
Next, your IsAdmin function really just needs to return a true/false value based on your lookup, and then the AdminPortal action can decide what to do with that result.
Since email seems to be a unique field in your table, use the SingleOrDefault or FirstOrDefault LINQ extension to fetch a single matching record. Which one you use is up to you, but if it's truly a unique value, SingleOrDefault makes more sense (it will throw an exception if more than one row matches). Using the StringComparison flag with the String.Equals extension method makes your search case-insensitive. There are a few culture-specific versions of that, but ordinal matching is what I would normally use, here.
Implementing some version of the Identity framework is a bit too long for an answer here, but it's possible to implement a claims-based authentication scheme without too much work. That's something that probably needs a separate answer, though.

Entity Framework randomly inserting many duplicate records over a long period of time

Rarely and apparently randomly, entity framework will insert many duplicate records. Can anyone explain why this behaviour occurs? This is the second project i've seen this in:
protected void btnAddQual_Click(object sender, EventArgs e)
{
QEntities ds = new QEntities();
Qualification qual = new Qualification();
qual.PersonID = ds.Persons.Where(x => x.Username == User.Identity.Name).Single().PersonID;
qual.QualificationName = txtQualAddName.Text;
qual.QualificationProvider = txtQualAddProvider.Text;
qual.QualificationYear = txtQualAddYear.Text;
qual.Inactive = false;
qual.LastUpdatedBy = User.Identity.Name;
qual.LastUpdatedOn = DateTime.Now;
ds.Qualifications.Add(qual);
ds.SaveChanges();
}
Qualifications Table:
public partial class Qualification
{
public int QualificationID { get; set; }
public int PersonID { get; set; }
public string QualificationName { get; set; }
public string QualificationProvider { get; set; }
public string QualificationYear { get; set; }
public bool Inactive { get; set; }
public string LastUpdatedBy { get; set; }
public Nullable<System.DateTime> LastUpdatedOn { get; set; }
public virtual Persons Persons { get; set; }
}
I've seen it create from three to 32 records in one button click, and when it does, the timestamps which can be spread across a good period of time (last time was 28 records, all identical apart from the primary key and timestamps, unevenly distributed over 23 minutes)
I've previously put this down to user or browser based behaviour, but last night it happened with me using the machine.
I didn't notice anything unusual at the time, but its infrequent occurance makes it a devil to track down. Can anyone suggest a cause?
Edit with additional information:
This is with .net framework 4.5.2 and EF 6.1.3
Edit to explain the bounty:
I've just seen this occur in the following code:
using(exEntities ds = new exEntities())
{
int initialStations;
int finalStations;
int shouldbestations = numStations * numSessions * numRotations * numBlock;
initialStations = ds.Stations.Count();
for(int b = 1; b <= numBlock; b++)
{
for (int se = 1; se <= numSessions; se++)
{
for (int r = 1; r <= numRotations; r++)
{
for (int st = 1; st <= numStations; st++)
{
Stations station = new Stations();
station.EID = eID;
station.Block = b;
station.Rotation = r;
station.Session = se;
station.StationNum = st;
station.LastUpdatedBy = User.Identity.Name + " (Generated)";
station.LastUpdatedOn = DateTime.Now;
station.Inactive = false;
ds.Stations.Add(station);
}
}
}
}
ds.SaveChanges();
In this instance, the number of iterations of each of the loops were:
1, 2, 6, 5 respectively.
This one click (same timestamp) has duplicated the complete set of records
This is a case where you need to add logging into your application. Based on your code, I do not believe Entity Framework is duplicating your data, rather that your code is being triggered in ways that you are not catering for. Where I have seen EF duplicate records has been due to developers passing entities loaded from one DBContext and then associating them to entities created in a second DBContext without checking the DbContext and attaching them first. EF will treat them as "new", and re-insert them. From the code that you have provided, I do not believe this is the case, but it is something to watch out for.
Firstly, when dealing with web applications in particular, you should write any event handler or POST/PATCH API method to treat every call as untrustworthy. Under normal use these methods should do what you expect, but under abusive use or hacking attempts they can be called when they shouldn't, or carry payloads that they shouldn't. For example, you may expect that an event handler with a record ID of 1234 would only be fired when record 1234 is updated and the user pressed the "Save" button (once), however it is possible to:
Receive the event more than once if the client code does not disable the button on click until the event succeeds.
Receive the event without the client clicking the button when fired through a debugging tool.
Receive the event with ID 1234 when a different record is saved, thanks to a user changing the Record ID in a debugging tool.
Trust nothing, verify and log everything, and if something is out of place, terminate the session. (Force log-out)
For logging, beyond the standard exception logging, I would recommend adding Information traces with an additional compiler constant for a production debug build to monitor one of these cases where this event is getting tripped more than once. Personally I use Diagnostics.Trace and then hook a logging handler like Log4Net into it.
I would recommend adding something like the following:
#if DEBUG
Trace.TraceInformation(string.Format("Add Stations called. (eIS: {0})", eID));
Trace.TraceInformation(Environment.StackTrace);
#endif
Then when you do your count check and find a problem:
Trace.TraceWarning(string.Format("Add Station count discrepancy. Expected {0}, Found {1}", shouldBeStations, finalStations));
I put the compiler condition because Environment.StackTrace will incur a cost that you normally do not want in a production system. You can replace "DEBUG" with another custom compilation constant that you enable for a deployment to inspect this issue. Leave it running in the wild and monitor your trace output (database, file, etc.) until the problem manifests. When the warning appears you can go back through the Information traces to see when and where the code is being triggered. You can also put similar tracing in calls that loaded the screen where this event would be triggered to record IDs and User details to see if/how an event might have been triggered for the wrong eID via a code bug or some environmental condition or hack attempt.
Alternatively, for logging you can also consider adding a setting to your application configuration to turn on and off logging events based on logging levels or flags to start/stop capturing scenarios without re-deploying.
These kinds of issues are hard to diagnose and fix over something like StackOverflow, but hopefully logging will help highlight something you hadn't considered. Worst case, consider bringing in someone experienced with EF and your core tech stack short-term to have a look over the system as a second pair of eyes over the entire workings may also help point out potential causes.
One small tip. Rather than something like:
qual.PersonID = ds.Persons.Where(x => x.Username == User.Identity.Name).Single().PersonID;
use:
qual.PersonID = ds.Persons.Where(x => x.Username == User.Identity.Name).Select(x => x.PersonID).Single();
The first statement executes a "SELECT * FROM tblPersons WHERE..." where the entity isn't already cached and pulls back all columns, where only PersonID is needed. The second executes a "SELECT PersonID FROM tblPersons WHERE..."

Default value for ViewModel

I'm making a simple Search page in MVC with some filters in it. The filters are represented by properties in my ViewModel. My ViewModel is binded to a GET form in the cshtml so my filter will appears in the querystrings and the user will be able to bookmark his search.
What I want to do is to assign a default value to some of my filters.
My (simplified) ViewModel :
public class SearchViewModel
{
//Filter I want to set a default value to
public OrganizationType? OrganizationType {get; set;}
//Results of the search
public IEnumerable<ItemViewModel> Items {get; set;}
}
I'd like to set a default value for OrganizationType. I can't simply set it in the constructor of SearchViewModel because it depends on the current user :
public void InitViewModel(SearchViewModel vm)
{
vm.OrganizationType = _someLogic.GetDefaultValue(_currentUser);
}
First solution was simply to check if OrganizationType is null, then assign a default value :
public ActionResult Search(SearchViewModel vm)
{
if(vm.OrganizationType == null)
vm.OrganizationType = _someLogic.GetDefaultValue(_currentUser);
return View(vm);
}
But this solution doesn't work as a null value corresponds to an empty filter and it's a choice that the user can make. So I can't override it.
The second solution I tried was to specify that the default value of the controller should be null in the Search action :
public ActionResult Search(SearchViewModel vm = null)
{
if (vm == null)
{
vm = new SearchViewModel();
InitViewModel(vm);
}
...
return View(vm);
}
But in practice, the variable vm is never null, so the default values are never setted.
I also tried having two Action, one wihout a ViewModel where I instanciate a new ViewModel with the default values and then call the second action :
public ActionResult Search()
{
var vm = new SearchViewModel();
InitViewModel(vm);
//Simply call the second action with the initizalied ViewModel
return Search(vm);
}
public ActionResult Search(SearchViewModel vm)
{
...
return View(vm);
}
But it doesn't work because there is now an ambiguity between the two action, and asp.net doesn't know which one to choose.
So in summary, I'd like to find a way to set a default value for a ViewModel, without setting it in the constructor and overriding user choices.
Another way to say it, how can I distinguish an "empty" ViewModel from one where some values are binded from the form.
Any idea ?
Ok I think I found a solution to my own problem...
I can use the ModelState property of the controler to check it the ViewModel is empty or was binded from the form :
public ActionResult Search(SearchViewModel vm = null)
{
if (ModelState.Count == 0)
{
InitViewModel(vm);
}
...
return View(vm);
}
So if ModelState.Count equals to 0 it means that user didn't change any filters. So the form is empty and we can bind our default values. As soon as the user will change one of the filters or submit the request, the ModelState.Count will be greater than 0 so we shouldn't set the default value. Otherwise we would override an user choice.
The logic of what you're doing is a little iffy. Generally speaking, if a value is nullable then null is the default value. However, it seems that you're trying to make a distinction here between whether the value is null because it's not set or null because the user explicitly set it to null. This type of semantic variance is usually a bad idea. If null has a meaning, then it should always carry that meaning. Otherwise, your code becomes more confusing and bugs are generally introduced as a result.
That said, you can't count on ModelState having no items. I've honestly never played around with ModelState enough in scenarios where there's not post data, but it's possible there's some scenario where there's no post data and yet ModelState may have items. Even if there isn't, this is an implementation detail. What if Microsoft does an update that adds items to ModelState in situations where it previously had none. Then, your code breaks with no obvious reason why.
The only thing you can really count on here is whether the request method is GET or POST. In the GET version of your action, you can reasonably assume that the user has made no modifications. Therefore, in this scenario, you can simply set the value to whatever you like without concern.
In the POST version of your action, the user has made some sort of modification. However, at this point, there is no way to distinguish any more whether the value is null because it is or because the user explicitly wanted it to be. Therefore, you must respect the value as-is.

Loading multiple sets of data in a content page in ASP.NET MVC Entitiy Framework

I need to load multiple entity types in my View page. I am using ViewModel for this purpose. However, I need to make around 5-6 database calls to load each set of data and assign them to the relevant property of the ViewModel. I wonder if this is a recommended approach since it requires multiple database calls. Or, am I being over-concerned about this? Here is a snapshot from my code:
var model = new EntryListVM();
string userid = "";
if (ViewBag.CurrentUserId == null)
userid = User.Identity.GetUserId();
else
userid = ViewBag.CurrentUserId;
ViewBag.CurrentUserId = userid;
//First database call
model.DiscussionWall = db.DiscussionWalls.Find(wallId);
//Second database call to learn if the current students has any entry
model.DiscussionWall.DoesStudentHasEntry = db.Entries.Any(ent => ent.DiscussionWallId == wallId && ent.UserId == userid);
model.PageIndex = pageIndex;
//Third database call
model.TeacherBadges = db.Badges.Where(b => b.CourseId == model.DiscussionWall.CourseId && b.IsSystemBadge == false && b.IsEnabled == true).ToList();
//Fourth database call
model.Reactions = db.Reactions.Where(re => re.CourseId == model.DiscussionWall.CourseId).ToList();
int entryPageSize = Convert.ToInt32(ConfigurationManager.AppSettings["EntryPageSize"]);
int firstChildSize = Convert.ToInt32(ConfigurationManager.AppSettings["FirstChildSize"]);
List<ViewEntryRecord> entryviews = new List<ViewEntryRecord>();
bool constrainedToGroup = false;
if (!User.IsInRole("Instructor") && model.DiscussionWall.ConstrainedToGroups)
{
constrainedToGroup = true;
}
//Fifth database call USING VIEWS
//I used views here because of paginating also to bring the first
//two descendants of every entry
entryviews = db.Database.SqlQuery<ViewEntryRecord>("DECLARE #return_value int;EXEC #return_value = [dbo].[FetchMainEntries] #PageIndex = {0}, #PageSize = {1}, #DiscussionWallId = {2}, #ChildSize={3}, #UserId={4}, #ConstrainedToGroup={5};SELECT 'Return Value' = #return_value;", pageIndex, entryPageSize, wallId, firstChildSize, userid, constrainedToGroup).ToList();
model.Entries = new List<Entry>();
//THIS FUNCTION MAP entryviews to POCO classes
model.Entries = ControllerUtility.ConvertQueryResultsToEntryList(entryviews);
//Sixth database call
var user = db.Users.Single(u => u.Id == userid);
model.User = user;
I wonder if this is too much of a burden for the initial page load?
I could use SQL-View to read all data at once, but I guess I would get a too complicated data set to manage.
Another option could be using Ajax to load the additional results after the page loading (with the main data) is completed. For example, I could load TeacherBadges with AJAX after the page is being loaded.
I wonder which strategy is more effective and recommended? Are there specific cases when a particular strategy could be more useful?
Thanks!
It all depends on your scenario - different scenarios have different ways of doing things. There is no single right way of doing things that are similar in nature. What might work for me might not work for you. Ever heard that saying: there are many ways to kill a cat? Well this certainly applies to programming.
I am going to answer based on what I think you are asking. Your questions are very broad and not that specific.
However, I am not sure if this is a recommended approach since it
requires multiple database calls.
Sometimes you need to do one database call to get data, and sometimes you need to do more than one database call to get the data. For example:
User details with addresses: one call for user and one call for addresses
User details: one call
I am using ViewModel for this purpose.
Using view models for your views is a good thing. If you want to read up more on what I had to say about view models then you can go and read an answer that I gave on the topic:
What is ViewModel in MVC?
View models are ideal for when you have data that is coming from multiple datasets. View models can also be used to display data coming from one dataset, for example:
Displaying user details with multiple addresses
Displaying only user details
I read the data in the controller in separate linq statements, and
assign them to the relevant List property of the ViewModel.
I would not always return a list - it all depends on what you need.
If I have a single object to return then I will populate a single object:
User user = userRepository.GetById(userId);
If I have a list of objects to return then I will return a list of objects:
List<User> users = userRepository.GetAll();
It is of no use to return a single object and then to populate a list for this object:
List<User> user = userRepository.GetByUserId(userId).ToList();
Second option could be using SQL-View to read all data with one
database call, and then map them to the entities properly in
controller.
This is similar to your first question, how you return your data on the database level is up to you. It can be stored procedures or views. I personally prefer stored procedures. I have never used views before. Irrespective of what you choose your above mentioned repository methods should still look the same.
Third option could be using Ajax to load the additional results after
the page loading (with the main data) is completed.
You can do this if you want to. I would not do it if it is not really needed. I try to load data on page load. I try to get as much data on the screen before the page is fully loaded. There have been times that I had to go the AJAX route after the page was loaded. After the page was loaded I had to do an AJAX call to load my HTML table.
If you really just need to have data displayed then do just that. You do not need any fancy ways of doing this. Maybe later you need to change on screen data, then AJAX is cool to use.
I wonder which strategy is more effective and recommended? Are there
specific cases when a particular strategy could be more useful?
Let us say you want to display a list of users. We do a database call and return the list to the view. I do not normally use view models if I only return a list:
public class UserController : Controller
{
private IUserRepository userRepository;
private IAddressRepository addressRepository;
public UserController(IUserRepository userRepository, IAddressRepository addressRepository)
{
this.userRepository = userRepository;
this.addressRepository = addressRepository;
}
public ActionResult Index()
{
List<User> users = userRepository.GetAll();
return View(users);
}
}
And your view could look like this:
#model List<YourProject.Models.User>
#if (Model.Count > 0)
{
foreach (var user in Model)
{
<div>#user.Name</div>
}
}
If you need to get a single user's details and a list of addresses, then I will make use of a view model because now I need to display data coming from multiple datasets. So a user view model can look something like this:
public class UserViewModel
{
public UserViewModel()
{
Addresses = new List<Address>();
}
public int Id { get; set; }
public string Name { get; set; }
public List<Address> Addresses { get; set; }
}
The your details action method could look like this:
public ActionResult Details(int id)
{
User user = userRepository.GetById(id);
UserViewModel model = new UserViewModel();
model.Name = user.Name;
model.Addresses = addressRepository.GetByUserId(id);
return View(model);
}
And then you need to display the user details and addresses in the view:
#model YourProject.ViewModels.UserViewModel
<div>First Name: #Model.Name</div>
<div>
#if (Model.Addresses.Count > 0)
{
foreach (var address in Model.Address)
{
<div>#address.Line1</div>
<div>#address.Line2</div>
<div>#address.Line3</div>
<div>#address.PostalCode</div>
}
}
</div>
I hope this helps. It might be to broad of an answer but it can guide you on the correct path.
Includes for linked data
For linked data it's simple (you probably know this way):
var users = context.Users.Include(user => user.Settings).ToList();
It queries all users and pre-loads Settings for each user.
Use anonymous class for different data sets
Here is an example:
context.Users.Select(user => new
{
User = user,
Settings = context.Settings
.Where(setting => setting.UserId == user.Id)
.ToList()
}).ToList();
You still kinda need to choose your main query collection (Users in this case), but it's an option. Hope it helps.

Resources