Calling multiple model Entity Framework in MVC View - asp.net

I want to call multiple model in my ADD view. I'm having hard time to call issues model and systems model in one view. See below codes
IssuesController
public ActionResult GetIssues()
{
using(SSD_INTERIM db = new SSD_INTERIM())
{
var issueList = db.TBL_ISSUES.Where(x => x.STATUS != "Closed").ToList();
return Json(new { data = issueList }, JsonRequestBehavior.AllowGet);
}
}
[HttpGet]
public ActionResult GetSystems()
{
using(SSD_INTERIM db = new SSD_INTERIM())
{
var issueList = db.LIB_SSD_SYSTEMS.Where(x => x.ENABLED == "Y").ToList();
return Json(new { data = issueList }, JsonRequestBehavior.AllowGet);
}
}
[HttpGet]
public ActionResult AddOrEdit(int id = 0)
{
return View(new TBL_ISSUES());
}
AddOrEdit.cshtml (view)
#model ProjectName.Models.TLB_ISSUES
#{
Layout = null;
}
#using (Html.BeginForm("AddOrEdit","Issues", FormMethod.Post, new { onsubmit = "return SubmitForm(this)"}))
{
#Html.HiddenFor(model => model.ISSUE_ID)
<div>
#Html.LabelFor(model => model.SYSTEMNAME, "System", new { #class = "control-label" })
#* Put DropdownListFor for system from different model *#
</div>
<div>
#Html.LabelFor(model => model.ISSUE_DESC, "Description", new { #class = "control-label" })
#Html.EditFor(model => model.ISSUE_DESC, new { htmlAttributes = new {#class="form-control"}})
</div>
}
Screenshot of Entity Framework model
Hope you can help me with my issue, I want to populate system dropdown with datas from different model.

One option is to define a view model for your Add/Edit. I would recommend using a naming convention such as referring to that view as a "Detail" view rather than "AddOrEdit".
[Serializable]
public class IssueDetailViewModel
{
public IssueViewModel Issue { get; set; }
public ICollection<SystemSummaryViewModel> Systems { get; set; } = new List<SystemSummaryViewModel>();
}
[Serializable]
public class IssueViewModel
{
//.. relevant Issue fields matching what you will need to insert/update an Issue entity.
}
[Serializable]
public class SystemSummaryViewModel
{
public int SystemId { get; set; }
public string DisplayName { get; set; }
}
Then populate this model in the case of an Add:
var systems = db.LIB_SSD_SYSTEMS
.Where(x => x.ENABLED == "Y")
.Select(x => new SystemSummaryViewModel
{
SystemId = x.ID,
DisplayName = x.SYSTEMNAME
}).ToList();
var model = new IssueDetailViewModel
{
Issue = new IssueViewModel();
Sytems = systems
};
return View(model);
When you bind the controls in your view, the controls for the Issue use #model.Issue.{property} while your selections for Systems are provided by #model.Systems.
I do not recommend ever passing Entity classes to and from views, but instead get in the practice of defining view models. The model for a view is a separate concern to a data model (which the entity reflects). Passing entities around leads to all kinds of issues including risking exposing far more information about your data implementation than the client needs, sending more data over the wire than the client needs, performance issues arising from serialization and lazy loading, and vague errors occurring when working with detached entity instances. These errors are commonly due to missing data or multiple instances for the same data when deserialized from the client, and can be situational making them appear intermittently or otherwise hard to reproduce.
For larger models with many lookups, or cases where you have larger lookup lists that you'll want to implement something like auto-complete searching to find matches (addresses, etc.) then you'll likely want to look at performing Ajax calls back to the server to load lookups on demand or based on search criteria.

Related

Create a entity framework LINQ group join query to return a subset of properties from DTO and a list of another DTO, ASP.net core

So, I tried searching and couldn't really find an answer that was explicit enough and guided me to my solution so I thought I would add my problem and ultimately my solution for others to benefit.
Pardon my newness to SO (consider this my start of getting my reputation up), let me know if I do anything incorrect or forget anything.
I am trying to:
Create a controller that queries a database for all the users and their roles.
Return the list of unique users id's and email address with a List of roles.
The email address and roles are in separate tables and the pkey/fkey is the user id
The roles are returned as a list containing my AllUserInRolesDto
Every example I looked at on SO or other sites only provided examples of returning anonymous data objects back. I don't have a lot of LINQ query syntax so had me stuck for an hour or so.
Here is my DTO
namespace Lib.Dtos
{
public class AllUserInRolesDto
{
public string FullName { get; set; }
public string Email { get; set; }
public List<RoleDto> Roles { get; set; }
}
public class RoleDto
{
public string RoleName { get; set; }
}
}
I have a business layer that defines the LINQ query
public List<AllUserInRolesDto> UserAllRolesGet()
{
List<AllUserInRolesDto> getAllUsersRoles = (from u in _context.Users
join r in _context.UserInRoles on u.UserId equals r.UserId
into ur
select new Lib.Dtos.AllUserInRolesDto()
{
FullName = u.Fullname,
Email = u.Email,
Roles = ur //this was the problem line and what the docs were describing
}).ToList();
return getAllUsersRoles;
}
...and my controller
[HttpGet("GetAllUserRolesList")]
public IActionResult GetAllUserRolesList()
{
List<Lib.Dtos.AllUserInRolesDto> allUsers = _userBL.UserAllRolesGet();
return new JsonResult(allUsers);
}
my solution
After taking a step back for a second I realized I actually wasn't returning the right object back to my roles property...and so need to iterate over my roles and create a list from them. Here is what worked.
public List<AllUserInRolesDto> UserAllRolesGet()
{
List<AllUserInRolesDto> getAllUsersRoles = (from u in _Context.Users
join r in _context.UserInRoles on u.UserId equals r.UserId
into ur
select new Lib.Dtos.AllUserInRolesDto()
{
FullName = u.Fullname,
Email = u.Email,
Roles = .Select(x => new Lib.Dtos.RoleDto() { RoleName = x.RoleName }).ToList() //Changed to this
}).ToList();
return getAllUsersRoles;
}
Anyway, probably a pretty dumb mistake, but had me stuck for a bit. Maybe this helps someone in my same position or if someone has a comment of how I could have improved this or used a different approach I am open to hearing suggestions.
I assume you're using Entity Framework, and that you have your DB model defined with relationships. This means you don't need to use explicit JOINs in your queries: you can use navigation properties instead.
Your "business layer" (note that you don't necessarily always need a business layer) should only work with Entity types and should not use DTOs (as DTOs belong to your web-service, in the same way that View-Models belong to a web-application).
If your "business layer" just consists of predefined queries, I recommend defining them as static extension methods for your DbContext and returning IQueryable<T> instead of as materialized List<T> as this enables your consumers to perform further operations on them (such as additional filtering or sorting and paging).
I recommend doing it like this instead:
// Queries methods (i.e. "business layer" queries)
public static class QueriesExtensions
{
public static IQueryable<User> GetAllUsersAndTheirRoles( this MyDbContext db )
{
// I assume `UserInRoles` is a linking table for a many-to-many relationship:
return db
.UserInRoles
.Include( uir => uir.User )
.Include( uir => uir.Role )
.Select( uir => uir.User );
}
}
// Web-service controller:
[HttpGet("GetAllUserRolesList")]
[Produces(typeof(List<AllUserInRolesDto>)]
public async Task<IActionResult> GetAllUserRolesList()
{
List<User> usersList = await this.db
.GetAllUsersAndTheirRoles()
.ToListAsync();
List<Lib.Dtos.AllUserInRolesDto> usersListDto = usersList
.Select( u => ToDto( u ) )
.ToList();
return new JsonResult( usersListDto );
}
// Entity-to-DTO mapping functions (if you have a lot of DTOs and entities, consider using AutoMapper to reduce the tedium)
private static AllUserInRolesDto ToDto( User user )
{
return new AllUserInRolesDto()
{
FullName = user.FullName,
Email = user.Email,
Roles = user.Roles.Select( r => ToDto( r ) ).ToList()
};
}
private static RoleDto ToDto( Role role )
{
return new RoleDto()
{
RoleName = role.RoleName
};
}

.net core 2.0 web api custom property model binding validation

I'm using .net core web api to custom bind a query string property of &format=a,b,c into an enumerable list. Do this I'm using a custom IModelBinder as follows:
public class RequestedFormatEnumerationBinder : IModelBinder
{
public Task BindModelAsync(ModelBindingContext bindingContext)
{
var valueProviderResult = bindingContext.ValueProvider.GetValue(bindingContext.ModelName);
var value = valueProviderResult.FirstOrDefault(); // get the value as string
if (value == null)
{
bindingContext.Result = ModelBindingResult.Success(new List<RequestedFormat>() { RequestedFormat.Any });
return Task.CompletedTask;
}
var options = value.Split(",");
var potentials = Enum.GetNames(typeof(RequestedFormat));
var formats = options
.Where(c => potentials.Any(x => x.Equals(c, StringComparison.OrdinalIgnoreCase)))
.Select(c => (RequestedFormat)Enum.Parse(typeof(RequestedFormat), c, true))
.ToList();
bindingContext.Result = ModelBindingResult.Success(formats);
return Task.CompletedTask;
}
}
I'm then applying this to my model as follows:
[EnumDataType(typeof(RequestedFormat))]
[JsonConverter(typeof(StringEnumConverter))]
[BindProperty(BinderType = typeof(RequestedFormatEnumerationBinder))]
public IEnumerable<RequestedFormat> Formats { get; set; }
The model binder is called and correctly parses the input. Unfortunately the model always fails validation on the Formats property.
I've tried adding a validation attribute and adding a validation state entry to the binding context that suppresses validation but nothing seems to work.
What step am I missing?

DropDownListFor "the value is invalid" when setting navigation property

I am attempting to set a navigation property (foreign key) based on the return value from a DropDownList.
I have the following data model:
(Some properties and details omitted for the sake of brevity).
An invite, which has a Guid Id and a collection of guests.
public class Invite
{
public Guid InviteId { get; set; }
public virtual ICollection<Guest> Guests { get; set; }
}
A guest, with an Invite property linking it to invite.
public class Guest
{
public virtual Invite Invite { get; set; }
}
In my DataInitaliser I can correctly build these objects and they are sent to the database, for example:
new Invite()
{
InviteId = Guid.NewGuid(),
Name = "Bloggs",
AllDay = false,
Guests = new List<Guest>()
{
new Guest() { GuestId = Guid.NewGuid(), FirstName = "Joe", LastName = "Bloggs", Vip = false},
}
};
In my GuestController I build the list of possible invites and add it to the ViewBag for presenting in the view.
void PopulateInvite(object invite)
{
var query = db.Invites.Select(i => i).OrderBy(i => i.Name).ToList();
ViewBag.Invites = new SelectList(query, "InviteId", "Name", invite);
}
I present the list of objects in the Guest View like so:
#model Models.Guest
<div class="form-group">
#Html.LabelFor(model => model.Invite, new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.DropDownListFor(model => model.Invite, (IEnumerable<SelectListItem>)ViewBag.Invites, String.Empty)
#Html.ValidationMessageFor(model => model.Invite)
</div>
</div>
This correctly displays the expected values from the database.
The problem occurs when I post the values back to the GuestController.
The Post function for the create is pretty much the standard scaffold.
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<ActionResult> Create([Bind(Include="GuestId,FirstName,LastName,Vegetarian,Attending,Vip,Invite")] Guest guest)
{
if (ModelState.IsValid)
{
guest.GuestId = Guid.NewGuid();
db.Guests.Add(guest);
await db.SaveChangesAsync();
return RedirectToAction("Index");
}
this.PopulateInvite(guest.Invite);
return View(guest);
}
I've dug into the cause a little bit here and I think I understand the underlying problem. My function PopulateInvite, places InviteId into the collection which is a Guid, this is returned as a String (not a Guid?) which cannot be converted into an Invite object.
"The parameter conversion from type 'System.String' to type 'Models.Invite' failed because no type converter can convert between these types."
I did try changing my PopulateInvite collection so its populated with an actual Invite object like so:
void PopulateInvite(object invite)
{
var query = db.Invites.Select(i => i).OrderBy(i => i.Name).ToList().Select(i =>
{
return new
{
Invite = new Invite() { InviteId = i.InviteId },
Name = i.Name
};
});
ViewBag.Invites = new SelectList(query, "Invite", "Name", invite);
}
However this also fails with the same error as above, confusingly I am returned a String representation of the object, instead of the actual object itself.
ModelState["Invite"].Value.RawValue
{string[1]}
[0]: "Models.Invite"
So...what is the correct way to set way to set the navigation property based on the post from the form?
Should I act before ModelState.IsValid to change the Guid into an actual Invite object?
As this tutorial from asp.net suggests, should I add a property to hold an InviteId, instead of using an invite object? In the sample Department is unused so I don't really understand why it has been added - am I missing something?
public class Course
{
public int DepartmentID { get; set; }
public virtual Department Department { get; set; }
}
Some other better method?
You can't bind a complex component like model.Invite in a DropDownListFor:
#Html.DropDownListFor(model => model.Invite, (IEnumerable<SelectListItem>)ViewBag.Invites, String.Empty
You need to put a singular value like a int from ID. Try to replace the code above to:
#Html.DropDownListFor(model => model.Invite.InviteID, (IEnumerable<SelectListItem>)ViewBag.Invites, String.Empty
Well, the answer was in the actual tutorial I linked in the question. I needed to add an InviteId field to act as the foreign key then the actual object acts as the navigation property, as explained below both are required (not just one as I was using it).
Creating an Entity Framework Data Model for an ASP.NET MVC Application
The StudentID property is a foreign key, and the corresponding
navigation property is Student. An Enrollment entity is associated
with one Student entity, so the property can only hold a single
Student entity (unlike the Student.Enrollments navigation property you
saw earlier, which can hold multiple Enrollment entities).
The CourseID property is a foreign key, and the corresponding
navigation property is Course. An Enrollment entity is associated with
one Course entity.
Entity Framework interprets a property as a foreign key property if
it's named (for
example, StudentID for the Student navigation property since the
Student entity's primary key is ID). Foreign key properties can also
be named the same simply (for example,
CourseID since the Course entity's primary key is CourseID).

Asp.net MVC4 Dropwdownlist Retain Selected Value after Get

I have a drop down list that is included in the _layout view of my application.
What i am trying to do is populate the list with Data from a sql server and then based on the value selected redirect the user to another view.
All is working fine except that when the user click Enter/Search the value of dropdownlist gets defaulted to the first value. As i am currently transitioning from Web Forms it's quite difficult and frustrating.
Here is the code for my Model
public class DomainNameViewModel
{
private static readonly string ConStr = WebConfigurationManager.ConnectionStrings["App"].ConnectionString.ToString();
public string SelectedDomainId { get; set; }
public IEnumerable<SelectListItem> domains
{
get
{
List<SelectListItem> l = new List<SelectListItem>();
using (SqlConnection con = new SqlConnection(ConStr))
{
SqlCommand com = new SqlCommand("spDomainList", con);
con.Open();
SqlDataReader sdr = com.ExecuteReader();
while (sdr.Read())
{
l.Add(new SelectListItem() { Text = sdr[0].ToString(), Value = sdr[1].ToString() });
}
return l;
}
}
The Code for controller.
[ChildActionOnly]
public ActionResult Index()
{
return PartialView(new DomainNameViewModel());
}
The DomainName View
#model app.Models.DomainNameViewModel
#{
Layout = null;
}
#Html.DropDownListFor(x => x.SelectedDomainId, Model.domains, new { #id = "e1",#class = "bannerlist" })
And the code for _Layout view
#using (Html.BeginForm("Search","DomainSearch",FormMethod.Get))
{
#Html.TextBox("txtDomain", null, new { #class = "bannertextbox" , placeholder="Search for a Perfect Domain!" })
#Html.Action("index","DomainName")
<input type="submit" class="bannerbutton" value="Search" />
}
Any help would be appreciated.
EDIT: Added the DomainSearchController code.
public class DomainSearchController : Controller
{
//
// GET: /DomainSearch/
public ActionResult Search(string txtDomain,string SelectedDomainId)
{
DomainNameViewModel Domain = new DomainNameViewModel();
Domain.SelectedDomainId = SelectedDomainId;
string check = Domain.ParseDomain(HttpUtility.HtmlEncode(txtDomain), HttpUtility.HtmlEncode(SelectedDomainId));
string s = Domain.CheckDomains(check);
ViewBag.Domain = Domain.DomainCheckResult(s);
return View();
}
}
You haven't quite shown/explained how exactly are you performing the redirect. But you will need to either pass the selected value in the query string to the target page or store in in a cookie or somewhere on the server like in the ASP.NET Session (beurk).
The reason you need to do this is because in ASP.NET MVC, contrary to classic WebForms there's no ViewState and you cannot retrieve the selected value on subsequent PostBack (there's neither such notion as PostBack in ASP.NET MVC).
Then in your child controller action you will need to retrieve the selected value from the query string or from the cookie or from the ASP.NET session (beurk) and set the SelectedDomainId property on your view model to this value.
For example:
[ChildActionOnly]
public ActionResult Index()
{
var model = new DomainNameViewModel();
// here you need to set the SelectedDomainId property on your view model
// to which the dropdown is bound to the selected value
model.SelectedDomainId = Request["domain_id"];
return PartialView(model);
}
Assuming you decide to pass this value as query string parameter when redirecting you will need to keep this parameter on subsequent redirects in order to keep the state.

Cascading dropdown with Static data

i need to develop two dropdown list in my MVC3 project, where first dropdown will be a db driven and based on the data selected on first dropdown my second dropdown should change. Second dropdown is having hard coded text and value. how could i achived this. please help
Following is my codebased
viewmodel
public IEnumerable<SelectListItem> ProductTypeCode{ get; set; }
public int? ProductID { get; set; }
public IEnumerable<SelectListItem> ProductGroupCode;
public int? ProductGrpID { get; set; }
Controller
public ActionResult Index() {
var model = new MyViewModel
{
// TODO: Fetch those from your repository ,
model.ProductTypeCode= new SelectList(obj.ProductTypeCode as System.Collections.IEnumerable, "Value", "DispalyText");
return view(model);
}
VIEW
#Html.DropDownListFor(m => m.ProductID , Model.ProductTypeCode, "ALL" , new { #class = "DropDownList" })</td>
My question is based on the above dropdown of productType, i need to populate another dropdown called "ProductGroup" which is having hardcoded value. based on the ProductType dropdown the value of productgroup should change.
You have several options.
You can write your own javascript to handle the onchange event. There are literally thousands of simple tutorials on this exact topic. This one will probably address your question the best.
You can use extensions like Telerik's. They're relatively straight forward, well documented, but do have the extra weight of another library on them (although the markup they generate is pretty slim).
If you go with option 1 and write your own, the two main things you'll have to add in will be an extra controller action and some javascript.
The javascript will listen for change events on your product Id drop down. Put this in a document ready function on the page itself.
$("#ProductID").change(function() {
// Get the product id selected
var id = $(this).val();
// Fire off an ajax request to get the groups
$.ajax({
// whatever the url may be.
url: "#Url.Action("Groups")" + id, // Append the id to the url.
dataType: "json",
type: "POST",
error: function() {
alert("An error occurred.");
},
success: function(data) {
var items = "";
$.each(data, function(i, item) {
items += "<option value=\"" + item.Id+ "\">" + item.Name + "</option>";
});
// Set the secondary dropdown content to the newly created
// list of options. Use whatever Id your secondary is.
$("#ProductGroup").html(items);
});
The controller action responds to the ajax call with the list of appropriate product groups.
public class ProductsController : Controller
{
public ActionResult Groups(int id)
{
// You only need the id and a name field in the response, not the
// entire object.
var groups = _myService.FindGroupsForProductId(id)
.Select(g => new
{
Id = g.Id,
Name = g.Name
});
// Return a json result. You only need to re
return Json(groups);
}
}
The code above should help you get started. It's assuming several things that you didn't show in your code at all. Your question got downvoted because at first, you didn't post any code. Then, the code that you did post doesn't really show that you've put any effort into finding the solution on your own. Find some jquery tutorials on this, there are thousands, then if you have specific problems, bring them here.

Resources