DropDownListFor "the value is invalid" when setting navigation property - asp.net

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).

Related

Calling multiple model Entity Framework in MVC View

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.

ParitionKey extracted from document doesn't match the one specified in the header - C#

I'm new to CosmosDB and trying to figure out what's going on. I am using Microsoft.Azure.Cosmos NuGet package for development.
This is the line that creates my container:
Container = await database.CreateContainerIfNotExistsAsync(Program.ContainerId, "/id", 400)
This is my class:
public class REProperty
{
public const string PartitionKey = "id";
[JsonProperty(PropertyName = "id")]
public string Id { get; set; }
public string Number { get; set; }
public User Owner { get; set; }
And finally the code where I try to create a new document:
ItemResponse<REProperty> Response = await Program.Container.CreateItemAsync<REProperty>(C, new PartitionKey(REProperty.PartitionKey));
I am using the exact same PartitionKey everywhere yet I am still getting this error every time. Am I missing anything obvious?
Error message:
(Message: {"Errors":["PartitionKey extracted from document doesn't match the one specified in the header"]
You've defined the collection to use the id property as the partition key. The value given as id will then be the partition key used. However, you are specifying this:
ItemResponse<REProperty> Response = await Program.Container.CreateItemAsync<REProperty>(C, new PartitionKey(REProperty.PartitionKey));
This will always set the value "id" as the partition key, which is not correct. The actual value is different from document to document. So either you set it like this: new PartitionKey(C.Id) or you just omit the partition key part in the item creation - I think it should be enough to just have the property set, but give it a try to check it.

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
};
}

How can I store more than 2 values inside a SelectListItem

I am working on two different databases , which store IP addresses, so I am populating a dropdown which shows the IP address from two database as follow:-
public JsonResult LoadRelatedIPs(string searchterm, string Searchby)
{
var ITsysips = repository.getTechnologyIPs(searchterm, Searchby).Select(a => a.IPAddress).ToList();
var it360ips = repository.getIT360TechnologyIPs(searchterm, Searchby).Select(a => a.IPADDRESS).ToList();
var join = ITsysips.Union(it360ips).ToList();
var CSData = join.Select(m => new SelectListItem()
{
Text = m,
Value = m,
});
return Json(CSData.OrderBy(a => a.Text), JsonRequestBehavior.AllowGet);
}
But the problem I am facing is that when I post the value back to my controller I am unable to determine the location of the IP, and to do so I need to query the two databases to know the source of the IP address, my model looks as follow:-
public partial class ITsysSwitchPort
{
public int TechnologyID { get; set; }
public int SwitchID { get; set; }
public string PortNumber { get; set; }
public Nullable<int> ITsysTechnologyIPID { get; set; }
public Nullable<long> IT360NetworkID { get; set; }
public virtual Technology Technology { get; set; }
public virtual TechnologyIP TechnologyIP { get; set; }
public virtual ITsysSwitch ITsysSwitch { get; set; }
}
where the ITsysTechnologyIPID & IT360NetworkID stores the ID of the IP address from the 2 databses.(of course for certain record one of the values will be null)
So my question is wheatear I can pass additional values inside my SelectListitem, containg the ITsysTechnologyIPID & IT360NetworkID ? So that I do not need to query the DB to re-check the ip location ?
currently inside my view displaying the IP address dropdownlist using a field named generalIP as follow, which get populated on runtime using jQuery:-
<span class="f">Site #Html.DisplayNameFor(model=>model.GeneralIP)</span>
#Html.DropDownListFor(model => model.GeneralIP, Enumerable.Empty<SelectListItem>())
#Html.ValidationMessageFor(model => model.GeneralIP)
Thanks
The <option> element has pretty much this syntax:
<option value="value-of-option"
label="display-text-of-option"
>
option-content
</option>
The display text for the option is either the content of the <option> element or the value of its label attribute. The label attribute is used in preference to the content.
If no value attribute is present, the value of the option (what gets submitted with the form) is the display text. If the value attribute is present, its value is submitted with the form.
You can put whatever you want into the value attribute: if you wanted to encode a serialized and encrypted representation of an entire object instance as the value of an <option>, you could [theoretically] do so.
When the form is submitted to the server, the meaning and interpretation of that value is entirely up to the receiving app.
So, yes, you can do what you ask. You might want to consider the security implications of sending internal data down to the client. Encrypting the option values might suggested so as to prevent user agents from mucking about with it.
One way to do this is join the string into the Value so that you have the ID and IP.
e.g. id+"-"+IP
Then when you post to controller, split the string from the - and you'll have the ID and IP address.
But as Nicholas Carey said, SelectListItem = <option> which can only have text & value.
What about to "mark" data (value of option) with source prefix?
var CSData = join.Select(m => new SelectListItem()
{
Text = m,
Value = String.Format("{0}_{1}", ITsysips.Contains(m) ? "SysIPs" : "360IPs" ,m),
});
Then when working with selected value from option you can check like:
if(valueFromOption.StartsWith("SysIPs"))
{
// it is from getTechnologyIPs method
}
else
{
// etc...
}

Using a custom method in LINQ to Entities

I have a LINQ expression:
var users = db.Relationships.Where(i => i.RelationshipId== relId)
.Select(s => s.User).Distinct().Select(s => new UserViewModel() {
Username = s.Username,
LastActiveDateTime = s.LastActive, // DateTime, I want it to be a string filtered through my custom GetFriendlyDate method
}).ToList();
// convert all DateTimes - yuck!
foreach (var userViewModel in users) {
userViewModel.LastActive = DateFriendly.GetFriendlyDate(userViewModel.LastActiveDateTime);
}
This solution is working, but it feels wrong to
have to iterate over all users after getting them from the db, just to reformat a property on every single one
have a DateTime property on my ViewModel just so that it can later be converted to a string and never touched again
Is there any way I can use the GetFriendlyDate method directly within the query?
Possible solutions, worth to mention:
Have a getter property of your ViewModel, which would return transformed string, something like:
public string LastActive
{
get
{
return DateFriendly.GetFriendlyDate(LastActiveDateTime);
}
}
Though it not solves your problem with existing LastActiveDateTime column, transformation will be applied only at moment of usage (in your view, most likely - anyways if you will try use it somewhere latter in query, it will not work for reason you already know), so no need to iterate manually.
Create View, which will transform data on server side; so your data will already be returned in format you need, if you're using DBFirst, probably, it's easiest and fastest solution;
Finally, you can use ToList() twice, once before selecting new ViewModel() (or call AsEnumerable(), or find other way to materialize query). It will fetch data from database and will allow you perform any C#-side functions you want directly in query after ToList(). But, as mentioned before - it's about getting all data, which matched criteria up to ToList() - in most cases it's not appropriate solution.
And here is some additional readings:
How can I call local method in Linq to Entities query?
I tested it in LINQpad and it works.
It still kinda iterates over users (with LINQ) but you don't have to add DateTime property to your viewmodel class. Also you could convert collection of Users to collection of UserViewModel objects with Automapper. It would still iterate over users of course but you wouldn't see it.
Had to create some setup code of course because I don't have your database.
void Main()
{
var db = new List<User> {
new User { LastActive = DateTime.Now, Username = "Adam", Lastname = "Nowak" },
new User { LastActive = DateTime.Now.AddYears(1), Username = "Eve", Lastname = "Kowalska"}
};
// select only properties that you need from database
var users = db
.Select(user => new { Username = user.Username, LastActive = user.LastActive})
.Distinct()
.ToList();
var usersVM = from u in users
select new UserViewModel { Username = u.Username, LastActiveDateTime = DateFriendly.GetFriendlyDate(u.LastActive)};
usersVM.Dump();
}
class User
{
public DateTime LastActive;
public string Username;
public string Lastname;
};
class UserViewModel
{
public string Username;
public string LastActiveDateTime;
}
static class DateFriendly
{
public static string GetFriendlyDate(DateTime date)
{
return "friendly date " + date.Year;
}
}
And this outputs
Username LastActiveDateTime
Adam friendly date 2013
Eve friendly date 2014
There is no direct Concert.ToDate method available for LINQ. But you can try using the DateAdd method from the SqlFunctions class:
var users = db.Relationships.Where(i => i.RelationshipId== relId)
.Select(s => new
{
s.User.Username,
LastActive=SqlFunctions.DateAdd("d",0, s.LastActive)
})
.ToList().Select(s => new UserViewModel()
{
Username = s.Username,
LastActiveDateTime = s.LastActive
});
Wouldn't the following work?
var users = db.Relationships.Where(i => i.RelationshipId== relId)
.Select(s => s.User).Distinct().Select(s => new UserViewModel() {
Username = s.Username,
LastActiveDateTime = DateFriendly.GetFriendlyDate(s.LastActive)
}).ToList();

Resources