ASP .NET Core 3 - Setup Identity Results in Errors - asp.net-core

I am trying to setup a new asp .net core 3 project. I am trying to asp .net identity into it. When I attempt to create a database migration, I get the following error;
An error occurred while accessing the Microsoft.Extensions.Hosting services. Continuing without the application service provider. Error: GenericArguments[0], 'Microsoft.AspNetCore.Identity.IdentityUser', on 'Microsoft.AspNetCore.Identity.EntityFrameworkCore.UserOnlyStore`6[TUser,TContext,TKey,TUserClaim,TUserLogin,TUserToken]' violates the constraint of type 'TUser'.
I've spent all afternoon trying to figure this out, but I can't figure out what this means, or more importantly, how to fix it.
My program class looks like this:
public class Program
{
public static void Main(string[] args)
{
CreateHostBuilder(args).Build().Run();
}
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>();
});
}
My Startup.cs file looks like this:
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<McClureDbContext>(options =>
options.UseSqlServer(
Configuration.GetConnectionString("DefaultConnection")));
services.AddDefaultIdentity<IdentityUser>(options => options.SignIn.RequireConfirmedAccount = true)
.AddEntityFrameworkStores<McClureDbContext>();
services.AddRazorPages();
services.Configure<IdentityOptions>(options =>
{
// Password settings.
options.Password.RequireDigit = true;
options.Password.RequireLowercase = true;
options.Password.RequireNonAlphanumeric = true;
options.Password.RequireUppercase = true;
options.Password.RequiredLength = 6;
options.Password.RequiredUniqueChars = 1;
// Lockout settings.
options.Lockout.DefaultLockoutTimeSpan = TimeSpan.FromMinutes(5);
options.Lockout.MaxFailedAccessAttempts = 5;
options.Lockout.AllowedForNewUsers = true;
// User settings.
options.User.AllowedUserNameCharacters =
"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-._#+";
options.User.RequireUniqueEmail = false;
});
services.ConfigureApplicationCookie(options =>
{
// Cookie settings
options.Cookie.HttpOnly = true;
options.ExpireTimeSpan = TimeSpan.FromMinutes(5);
options.LoginPath = "/Account/Login";
options.AccessDeniedPath = "/Account/AccessDenied";
options.SlidingExpiration = true;
});
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/Error");
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapRazorPages();
});
}
}
My DbContext Class looks like this:
public class McClureDbContext : Microsoft.AspNetCore.Identity.EntityFrameworkCore.IdentityDbContext<ApplicationUser, ApplicationRole, Guid>
{
public McClureDbContext()
{
}
public McClureDbContext(DbContextOptions<McClureDbContext> options): base(options)
{
}
public DbSet<Home> Homes { get; set; }
public DbSet<HomePicture> HomePictures { get; set; }
public DbSet<HomeVideo> HomeVideos { get; set; }
public DbSet<ApplicationUser> AspNetUsers { get; set; }
public DbSet<Province> Province { get; set; }
public DbSet<Country> Country { get; set; }
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
base.OnConfiguring(optionsBuilder);
var builder = new ConfigurationBuilder()
.SetBasePath(System.IO.Directory.GetCurrentDirectory())
.AddJsonFile("appsettings.json");
var Configuration = builder.Build();
var connString = Configuration.GetConnectionString("DefaultConnection");
optionsBuilder.UseSqlServer(connString, options => options.EnableRetryOnFailure());
}
protected override void OnModelCreating(ModelBuilder builder)
{
base.OnModelCreating(builder);
builder.Entity<Home>().Property(n => n.Sold).HasDefaultValue(false);
}
}
My three data types (for the moment), look like this:
public class Home
{
public Home()
{
}
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public Int32 HomeId { get; set; }
public Guid AspNetUsersId { get; set; }
[ForeignKey("AspNetUsersId")]
public ApplicationUser ApplicationUser { get; set; }
[MaxLength(100)]
public string Address1 { get; set; }
[MaxLength(100)]
public string Address2 { get; set; }
[MaxLength(100)]
public string City { get; set; }
public Int64 ProvinceId { get; set; }
[ForeignKey("ProvinceId")]
public Province Province { get; set; }
[MaxLength(20)]
public string PostalCode { get; set; }
public double? Latitude { get; set; }
public double? Longitude { get; set; }
public bool Sold { get; set; }
public ICollection<HomeVideo> HomeVideos { get; set; }
public ICollection<HomePicture> HomePictures { get; set; }
[DatabaseGenerated(DatabaseGeneratedOption.Computed)]
public DateTime DateEntered { get; set; }
[DatabaseGenerated(DatabaseGeneratedOption.Computed)]
public DateTime DateUpdated { get; set; }
}
public class HomeVideo
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public Int32 HomeVideoId { get; set; }
public Int32 HomeId { get; set; }
[ForeignKey("HomeId")]
public Home Home { get; set; }
[MaxLength(500)]
public string Description { get; set; }
[MaxLength(1024)]
public string VideoUrl { get; set; }
[DatabaseGenerated(DatabaseGeneratedOption.Computed)]
public DateTime DateEntered { get; set; }
[DatabaseGenerated(DatabaseGeneratedOption.Computed)]
public DateTime DateUpdated { get; set; }
}
public class HomePicture
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public Int32 HomePictureId { get; set; }
public Int32 HomeId { get; set; }
[ForeignKey("HomeId")]
public Home Home { get; set; }
[MaxLength(500)]
public string Description { get; set; }
[MaxLength(1024)]
public string PictureUrl { get; set; }
[DatabaseGenerated(DatabaseGeneratedOption.Computed)]
public DateTime DateEntered { get; set; }
[DatabaseGenerated(DatabaseGeneratedOption.Computed)]
public DateTime DateUpdated { get; set; }
}
I don't remember having any of these types of issues when I built stuff in asp .net core 2.x. I figure that I am doing something wrong, but have no idea how to fix it. Searching google has not been much help today. I am at a loss. Any ideas/directions are appreciated.
TIA,
Wally

Related

Blazor Server IHttpContextAccessor returning null when deployed

I am dynamically setting the connection string based on the user that is logged in. This is working perfectly when running locally/debugging but when its deployed it's returning a null reference exception. This is my DbContext and I am handling the connection string logic in the OnConfigure method:
namespace SwordfishCRM.Services.ApplicationDbContext
{
public class PrimaryApplicationDbContext : IdentityDbContext<ApplicationUser>
{
private readonly HttpContext context;
private readonly IConfiguration configuration;
public PrimaryApplicationDbContext(DbContextOptions<PrimaryApplicationDbContext> options, IHttpContextAccessor httpContextAccessor, IConfiguration configuration) : base(options)
{
this.context = httpContextAccessor.HttpContext;
this.configuration = configuration;
}
//Tables
public DbSet<Address> Addresses { get; set; }
public DbSet<ChimneySweepCycle> ChimneySweepCycles { get; set; }
public DbSet<ChimneySweepMethod> ChimneySweepMethods { get; set; }
public DbSet<ContactMethod> ContactMethods { get; set; }
public DbSet<ContactPreference> ContactPreferences { get; set; }
public DbSet<Customer> Customers { get; set; }
public DbSet<Diary> Diary { get; set; }
public DbSet<DiaryDate> DiaryDates { get; set; }
public DbSet<DiaryOption> DiaryOptions { get; set; }
public DbSet<Gender> Genders { get; set; }
public DbSet<Job> Jobs { get; set; }
public DbSet<JobActualLine> JobActualLines { get; set; }
public DbSet<JobApproval> JobApprovals { get; set; }
public DbSet<JobQuoteLine> JobQuoteLines { get; set; }
public DbSet<JobStatus> JobStatuses { get; set; }
public DbSet<JobType> JobTypes { get; set; }
public DbSet<Product> Products { get; set; }
public DbSet<Report> Reports { get; set; }
public DbSet<RetroSweepingCertificate> RetroSweepingCertificates { get; set; }
public DbSet<ScheduleDay> ScheduleDays { get; set; }
public DbSet<ScheduleExceptionDay> ScheduleExceptionDays { get; set; }
public DbSet<Title> Titles { get; set; }
//Views
public virtual DbSet<vwAvailableApoointment> vwAvailableApoointments { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder
.Entity<vwAvailableApoointment>(eb =>
{
eb.HasNoKey();
eb.ToView("vwAvailableApoointments");
});
}
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
var userId = context.User.Identity.Name;
if (userId == null)
{
optionsBuilder.UseSqlServer(configuration.GetConnectionString("Master"));
}
else
{
var prefix = IdentitySupport.GetConnectionPrefix(userId);
optionsBuilder.UseSqlServer(configuration.GetConnectionString(prefix));
}
}
}
}
This is the console error when it's been deployed:
Error: System.NullReferenceException: Object reference not set to an instance of an object.
at SwordfishCRM.Services.ApplicationDbContext.PrimaryApplicationDbContext.OnConfiguring(DbContextOptionsBuilder optionsBuilder) in D:\a\1\s\SwordfishCRM.Services\ApplicationDbContext\PrimaryApplicationDbContext .cs:line 67
at Microsoft.EntityFrameworkCore.DbContext.get_ContextServices()
at Microsoft.EntityFrameworkCore.DbContext.get_Model()
at Microsoft.EntityFrameworkCore.Internal.InternalDbSet`1.get_EntityType()
at Microsoft.EntityFrameworkCore.Internal.InternalDbSet`1.CheckState()
at Microsoft.EntityFrameworkCore.Internal.InternalDbSet`1.get_EntityQueryable()
at Microsoft.EntityFrameworkCore.Internal.InternalDbSet`1.System.Linq.IQueryable.get_Provider()
at System.Linq.Queryable.OrderBy[TSource,TKey](IQueryable`1 source, Expression`1 keySelector)
at SwordfishCRM.Web.Pages.Swordfish.DiaryScheduleExceptionDates.OnInitializedAsync() in D:\a\1\s\SwordfishCRM.Web\Pages\Swordfish\DiaryScheduleExceptionDates.razor:line 25
at Microsoft.AspNetCore.Components.ComponentBase.RunInitAndSetParametersAsync()
I am confused why this run's locally but not when deployed. I would welcome any support.
Thank you
I have tried different variations of HTTPContext but to no avail. I am expecting it to find the logged in user, so I can retrieve a code to set the connection string.
There is a fail safe to use the config for the Master database (this is not the actual Master database, just a name for the main database with all the users etc...)
There is a step before this that if they are not logged in, they wont even hit this screen.
It turned out I needed AuthenticationStateProvider, then I needed to place a wait until the the prefix was returned. This is working a treat!!
protected override async void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
var authstate = await authenticationStateProvider.GetAuthenticationStateAsync();
var userId = authstate.User;
if (userId == null)
{
optionsBuilder.UseSqlServer(configuration.GetConnectionString("Master"));
}
else
{
var prefix = IdentitySupport.GetConnectionPrefix(userId.Identity.Name);
await Task.CompletedTask;
optionsBuilder.UseSqlServer(configuration.GetConnectionString(prefix));
}
}

Using existing .Net Identity database in .NET CORE application

im new to identity core and am trying to create a mvc app using .Net Core 3.1.1 and hit a roadblock on identity.
My toughest restriction is that I need to use the old Identity database from an old MVC5 app. The app is in use and this means the table structure cannot be changed.
Followed the steps described in Sql Exeption: Invalid column name "NormalizedUserName..." in existing .net framework identiy db used by .net core project
Got Usermanager and models set up in startup:
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(
Configuration.GetConnectionString("RaksaConnection")));
services.AddDbContext<RaksaDbContext>(options=>
options.UseSqlServer(
Configuration.GetConnectionString("RaksaConnection")));
services.AddIdentity<ApplicationUser, ApplicationRole>()
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddDefaultTokenProviders();
//services.AddTransient <UserManager<ApplicationUser>>();
services.AddControllersWithViews();
services.AddRazorPages();
}
And models copied over from older site:
public class ApplicationUser : IdentityUser
{
public ApplicationUser()
{
this.Id = Guid.NewGuid().ToString();
}
public string FirstName { get; set; }
public string LastName { get; set; }
private string PF_FName;
private string PF_FNameAndCompany;
public int? NeptonUserId { get; set; }
public string NeptonUserName { get; set; }
public int? NeptonUserPersonnelNumber { get; set; }
public virtual Guid CompanyDataId { get; set; }
public override string UserName { get; set; }
public override string NormalizedEmail { get { return this.Email.ToUpperInvariant(); } }
public override string NormalizedUserName { get { return this.UserName.ToUpperInvariant(); } }
public DateTime? LockoutEndDateUtc { get; set; }
private string PF_CompanyVat { get; set; }
private string PF_JobTitleValue { get; set; }
private DateTime PF_AccountCreated { get; set; }
public bool GIG_worker { get; set; }
public DateTime? LastLogin { get; set; }
public int CommLevelId { get; set; }
[Display(Name = "Nimi")]
public string FullName
{
get
{
this.PF_FName = FirstName + " " + LastName;
return this.PF_FName;
}
}
public DateTime AccountCreated
{
get
{
return this.PF_AccountCreated;
}
set
{
this.PF_AccountCreated = value;
}
}
private bool PF_Isrejected { get; set; }
public bool IsRejected
{
get
{
return this.PF_Isrejected;
}
set
{
this.PF_Isrejected = value;
}
}
public bool Active { get; set; }
public string JobTitle
{
get
{
return this.PF_JobTitleValue;
}
set
{
this.PF_JobTitleValue = value;
}
}
}
public class ApplicationRole : IdentityRole
{
public ApplicationRole()
{
base.Id = Guid.NewGuid().ToString();
}
public ApplicationRole(string name) : this()
{
base.Name = name;
}
public ICollection<Permission> Permissions { get; set; }
}
public class Permission
{
public Permission()
{
this.PermissionId = Guid.NewGuid().ToString();
this.RolesWithPermission = new HashSet<ApplicationRole>();
}
public string PermissionId { get; set; }
public string PermissionDescription { get; set; }
public ICollection<ApplicationRole> RolesWithPermission;
}
}
Can get the usermanager to list the users just fine, but SignInManager compalains about NormalizedUsername field:
InvalidOperationException: The LINQ expression 'DbSet<ApplicationUser>
.Where(a => a.NormalizedUserName == __normalizedUserName_0)' could not be translated.
Either rewrite the query in a form that can be translated, or switch to client evaluation
explicitly by inserting a call to either AsEnumerable(), AsAsyncEnumerable(), ToList(),
or ToListAsync(). See https://go.microsoft.com/fwlink/?linkid=2101038 for more information.
Don't know where to go next, do I have to customize signInmanager or is there a more simple solution?

Querying DTOs based on EF using Odata

I have an ASP.NET Core Web API setup with a SQL Server database and an EF data model.
Versions:
EF: Microsoft.EntityFrameworkCore 5.0.0-preview.7.20365.15
OData: Microsoft.AspNetCore.OData 7.4.1
.Net Core 3.1
The question is: I can't use filter, select in expand (nested expand). Example URLs which OData does not add filters to the where condition of SQL query which is seen on SQL Server Profiler:
https://localhost:44327/odata/clientcontract?$expand=ContactsInfo($filter=value eq '100003265')
https://localhost:44327/odata/clientcontract?$expand=Documents($filter=documentnnumber eq '100003265')
These are my database-first entity models:
public partial class ClientRef
{
public ClientRef()
{
Addresses = new HashSet<Address>();
Assets = new HashSet<Asset>();
ClientContactInfoComps = new HashSet<ClientContactInfoComp>();
ClientRelationCompClient1Navigations = new HashSet<ClientRelationComp>();
ClientRelationCompClient2Navigations = new HashSet<ClientRelationComp>();
Clients = new HashSet<Client>();
CommentComps = new HashSet<CommentComp>();
Companies = new HashSet<Company>();
Documents = new HashSet<Document>();
PhysicalPeople = new HashSet<PhysicalPerson>();
}
[Column("Inn")]
public int Id { get; set; }
public virtual ICollection<Address> Addresses { get; set; }
public virtual ICollection<Asset> Assets { get; set; }
public virtual ICollection<ClientContactInfoComp> ClientContactInfoComps { get; set; }
public virtual ICollection<ClientRelationComp> ClientRelationCompClient1Navigations { get; set; }
public virtual ICollection<ClientRelationComp> ClientRelationCompClient2Navigations { get; set; }
public virtual ICollection<Client> Clients { get; set; }
public virtual ICollection<CommentComp> CommentComps { get; set; }
public virtual ICollection<Company> Companies { get; set; }
public virtual ICollection<Document> Documents { get; set; }
public virtual ICollection<PhysicalPerson> PhysicalPeople { get; set; }
}
public partial class Document
{
public int Id { get; set; }
public string DocumentNumber { get; set; }
public int DocumentType { get; set; }
public int Inn { get; set; }
public DateTime ValidFrom { get; set; }
public DateTime ValidTo { get; set; }
public DateTime? DocumentExpireDate { get; set; }
public virtual ClientRef InnNavigation { get; set; }
}
public partial class ClientContactInfoComp
{
public int Id { get; set; }
public int Inn { get; set; }
public int ContactInfoId { get; set; }
public int? Point { get; set; }
public DateTime ValidFrom { get; set; }
public DateTime ValidTo { get; set; }
[ForeignKey("ContactInfoId")]
public ContactInfo ContactInfo { get; set; }
public virtual ClientRef InnNavigation { get; set; }
}
public partial class ContactInfo
{
public ContactInfo()
{
CallHistories = new HashSet<CallHistory>();
ClientContactInfoComps = new HashSet<ClientContactInfoComp>();
CommentComps = new HashSet<CommentComp>();
}
public int Id { get; set; }
public int? Type { get; set; }
public string Value { get; set; }
public virtual ICollection<CallHistory> CallHistories { get; set; }
public virtual ICollection<ClientContactInfoComp> ClientContactInfoComps { get; set; }
public virtual ICollection<CommentComp> CommentComps { get; set; }
}
public partial class CommentComp
{
public int Id { get; set; }
public int Inn { get; set; }
public int? CommentId { get; set; }
public int? ContactId { get; set; }
public virtual Comment Comment { get; set; }
public virtual ContactInfo Contact { get; set; }
public virtual ClientRef InnNavigation { get; set; }
}
public partial class Comment
{
public Comment()
{
CommentComps = new HashSet<CommentComp>();
}
public int Id { get; set; }
public string Text { get; set; }
public DateTime? CreateTimestamp { get; set; }
public string Creator { get; set; }
public virtual ICollection<CommentComp> CommentComps { get; set; }
}
These are my DTOs:
[DataContract]
public class ClientContract
{
public ClientContract()
{
ContactsInfo = new List<ContactInfoContract>();
Documents = new List<DocumentContract>();
Relations = new List<RelationContract>();
ClientComment = new CommentContract();
}
[DataMember(Name = "INN")]
[Key]
public int INN { get; set; }
[DataMember(Name = "validfrom")]
public DateTime ValidFrom { get; set; }
[DataMember(Name = "validto")]
public DateTime ValidTo { get; set; }
[DataMember(Name = "clienttype")]
public ClientType ClientType { get; set; }
[DataMember(Name = "companyname")]
public string CompanyName { get; set; }
[DataMember(Name = "firstname")]
public string FirstName { get; set; }
[DataMember(Name = "lastname")]
public string LastName { get; set; }
[DataMember(Name = "fathername")]
public string FatherName { get; set; }
[DataMember(Name = "pinnumber")]
public string PinNumber { get; set; }
[DataMember(Name = "birthdate")]
public DateTime? BirthDate { get; set; }
[DataMember(Name = "positioncustom")]
public string PositionCustom { get; set; }
[DataMember(Name = "position")]
public int Position { get; set; }
[DataMember(Name = "monthlyincome")]
public decimal MonthlyIncome { get; set; }
[DataMember(Name = "clientcomment")]
public CommentContract ClientComment { get; set; }
[DataMember(Name = "contactsinfo")]
public List<ContactInfoContract> ContactsInfo { get; set; }
[DataMember(Name = "documents")]
[ForeignKey("Documents")]
public List<DocumentContract> Documents { get; set; }
[DataMember(Name = "relations")]
public List<RelationContract> Relations { get; set; }
}
public class DocumentContract
{
[Key]
public int Id { get; set; }
[DataMember(Name = "documentNumber")]
public string documentNumber { get; set; }
[DataMember(Name = "documentType")]
public int documentType { get; set; }
[DataMember(Name = "documentexpiredate")]
public DateTime? documentExpireDate { get; set; }
}
[DataContract]
public class ContactInfoContract
{
public ContactInfoContract()
{
ContactComment = new CommentContract();
}
[Key]
public int Id { get; set; }
[DataMember(Name = "type")]
public int Type { get; set; }
[DataMember(Name = "value")]
public string Value { get; set; }
[DataMember(Name = "contactComment")]
public CommentContract ContactComment { get; set; }
}
public class CommentContract
{
[DataMember(Name = "Text")]
public string Text { get; set; }
[DataMember(Name = "creator")]
public string Creator { get; set; }
}
In DTOs there is not relation model. For instance: in EF, there is a ClientContactInfoComp model, which connects ClientRefs and ContactInfo models, but in DTO ClientContract is directly referenced with ContactInfoContract.
Model Builder in Startup.cs
private IEdmModel GetEdmModel()
{
var builder = new ODataConventionModelBuilder();
builder.EntitySet<ClientContract>("ClientContract").EntityType.HasKey(x => x.INN).Name = "ClientRef";
builder.EntitySet<DocumentContract>("DocumentContract");
builder.EntitySet<ContactInfoContract>("ContactInfoContracts").EntityType.HasKey(x => x.Id);
return builder.GetEdmModel();
}
public class ClientContractController : ControllerBase
{
[EnableQuery(MaxExpansionDepth = 10)]
public IQueryable<ClientContract> Get()
{
var clientRefs = _context.ClientRefs
.Include(x => x.Clients.Where(x => x.ValidFrom < DateTime.Now && x.ValidTo > DateTime.Now))
.Include(x => x.PhysicalPeople.Where(x => x.ValidFrom < DateTime.Now && x.ValidTo > DateTime.Now))
.Include(x => x.Companies.Where(x => x.ValidFrom < DateTime.Now && x.ValidTo > DateTime.Now))
.Include(x => x.Documents.Where(x => x.ValidFrom < DateTime.Now && x.ValidTo > DateTime.Now))
.Include(x => x.ClientContactInfoComps.Where(x => x.ValidFrom < DateTime.Now && x.ValidTo > DateTime.Now))
.ThenInclude(x => x.ContactInfo)
.Include(x => x.Assets.Where(x => x.ValidFrom < DateTime.Now && x.ValidTo > DateTime.Now));
List<ClientContract> contracts = new List<ClientContract>();
foreach (var clientRef in clientRefs)
{
ClientContract clientContract = new ClientContract() { INN = clientRef.Id };
foreach(var c in clientRef.Clients)
{
clientContract.ClientType = (ClientType) c.ClientType;
}
foreach (var pp in clientRef.PhysicalPeople)
{
clientContract.FirstName = pp.FirstName;
clientContract.LastName = pp.LastName;
}
foreach (var comp in clientRef.Companies)
{
clientContract.CompanyName = comp.CompanyName;
}
foreach (var doc in clientRef.Documents)
{
clientContract.Documents.Add(new DocumentContract()
{
documentNumber = doc.DocumentNumber,
documentExpireDate = doc.DocumentExpireDate,
documentType = doc.DocumentType
});
}
foreach (var comp in clientRef.ClientContactInfoComps)
{
clientContract.ContactsInfo.Add(new ContactInfoContract
{
Type = comp.ContactInfo.Type.Value,
Value = comp.ContactInfo.Value
});
}
contracts.Add(clientContract);
}
return contracts.AsQueryable();
}
}
Yes I getting result from following links:
https://localhost:44327/odata/clientcontract
https://localhost:44327/odata/clientcontract?$filter=Id eq 4
https://localhost:44327/odata/clientcontract?$expand=documents,contactsinfo
Startup.cs
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers();//.AddNewtonsoftJson(); ;
services.AddDbContext<DbContext>(options =>
options.UseSqlServer("connectionstring"));
services.AddOData();
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseHttpsRedirection();
app.UseRouting();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
//endpoints.EnableDependencyInjection();
endpoints.Expand().Select().Filter().OrderBy().Count().MaxTop(10);
endpoints.MapODataRoute("odata", "odata", GetEdmModel());
});
}
private IEdmModel GetEdmModel()
{
var builder = new ODataConventionModelBuilder();
builder.EntitySet<ClientContract>("ClientContract").EntityType.HasKey(x => x.INN).Name = "ClientRef";
builder.EntitySet<DocumentContract>("DocumentContract");
builder.EntitySet<ContactInfoContract>("ContactInfoContracts").EntityType.HasKey(x => x.Id);
return builder.GetEdmModel();
}
}
You should use the property name and not the attribute Name when use OData.
OData client library relies on it's own attribute OriginalNameAttribute to gain knowledge about class/member names as server emits them. The details you can see from here.
It works when I remove [DataContract] attribute.
public class ClientContract
{
public ClientContract()
{
ContactsInfo = new List<ContactInfoContract>();
}
[Key]
public int INN { get; set; }
public string CompanyName { get; set; }
public List<ContactInfoContract> ContactsInfo { get; set; }
}
public class ContactInfoContract
{
[Key]
public int Id { get; set; }
[DataMember(Name = "type")]
public int Type { get; set; }
[DataMember(Name = "value")]
public string Value { get; set; }
}

OData can not expand Many-to-Many

I can't expand nested table. Here is the example:
File:
public class File
{
public Guid Id { get; set; }
public string ContentType { get; set; }
public string Filename { get; set; }
public IEnumerable<FileFileStatus> FileFileStatuses { get; set; }
}
FileFileStatus:
public class FileFileStatus
{
public Guid FileId { get; set; }
public File File { get; set; }
public Guid FileStatusId { get; set; }
public FileStatus FileStatus { get; set; }
}
FileStatus:
public class FileStatus
{
public Guid Id { get; set; }
public string Status { get; set; }
public IEnumerable<FileFileStatus> FileFileStatuses { get; set; }
}
ModelConfiguration:
var files = builder.EntitySet<File>("Files").EntityType;
files.HasMany(f => f.FileFileStatus);
var fileFileStatuses = builder.EntityType<FileFileStatus>().HasKey(f => new { f.FileStatusId, f.FileId });
fileFileStatuses.HasRequired(f => f.FileStatus);
builder.EntityType<FileStatus>().HasKey(f => f.Id);
And here is how I am trying to get result:
https://localhost:44335/odata/Files?$top=10&$expand=FileFileStatuses
I am recieving empty FileFileStatuses
It works on me.
Expand
Without Expand
Codes of Models
public class File
{
public Guid Id { get; set; }
public string ContentType { get; set; }
public string Filename { get; set; }
public IEnumerable<FileFileStatus> FileFileStatuses { get; set; }
}
public class FileFileStatus
{
[Key]
public Guid Id { get; set; }
public Guid FileId { get; set; }
[ForeignKey("FileId")]
public File File { get; set; }
public Guid FileStatusId { get; set; }
[ForeignKey("FileStatusId")]
public FileStatus FileStatus { get; set; }
}
public class FileStatus
{
public Guid Id { get; set; }
public string Status { get; set; }
public IEnumerable<FileFileStatus> FileFileStatuses { get; set; }
}
Codes of Controller
[ODataRoutePrefix("Files")]
public class FilesController : ODataController
{
private readonly XxxDbContext xxxDbContext;
public FilesController(XxxDbContext xxxDbContext)
=> this.xxxDbContext = xxxDbContext;
[ODataRoute]
[EnableQuery]
public IActionResult Index()
{
return Ok(this.xxxDbContext.Files);
}
}
Startup.cs
public void ConfigureServices(IServiceCollection services)
{
services.AddControllersWithViews().AddNewtonsoftJson();
services.AddOData();
services.AddDbContext<XxxDbContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("XxxDbContext")));
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/Home/Error");
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllerRoute(
name: "default",
pattern: "odata/{controller=Home}/{action=Index}/{id?}");
endpoints.EnableDependencyInjection();
endpoints.Select().Expand().Filter().OrderBy().Count().MaxTop(10);
});
}

How to avoid saving a value of property of form object when saving changes to db

In a crud asp.net core 2.2 web app, I need to avoid saving a property of form object to db. How do I do that?
I've tried using [Editable(false)] data annotation on the ListBin property to prevent saving property value to db.
[Table("supply_lists")]
public partial class SupplyLists
{
[Column("id")]
public int Id { get; set; }
[Column("category_id")]
public int CategoryId { get; set; }
[Required]
[Column("coursecode")]
[StringLength(200)]
public string Coursecode { get; set; }
[Required]
[Column("title")]
[StringLength(200)]
public string Title { get; set; }
[Required]
[Column("filename")]
[StringLength(200)]
public string Filename { get; set; }
[Column("isactive")]
public bool Isactive { get; set; }
[Column("date", TypeName = "smalldatetime")]
public DateTime Date { get; set; }
[Column("list_bin")]
public byte[] ListBin { get; set; }
[ForeignKey("CategoryId")]
[InverseProperty("SupplyLists")]
public virtual SupplyListCategory Category { get; set; }
}
[ModelMetadataType(typeof(MetaDataTypeModel))]
public partial class SupplyLists
{
}
public class MetaDataTypeModel
{
[Editable(false)]
public byte[] ListBin { get; set; }
[Display(Name = "Is Active")]
public bool Isactive { get; set; }
[Display(Name ="Course Code")]
public string Coursecode { get; set; }
[Display(Name = "Category")]
public int CategoryId { get; set; }
[DataType(DataType.Date)]
public DateTime Date { get; set; }
}
public class EditModel : PageModel
{
private readonly SupplyListCore22.Models.SupplyListsContext _context;
private readonly IHostingEnvironment _env;
public EditModel(SupplyListCore22.Models.SupplyListsContext context, IHostingEnvironment env)
{
_context = context;
_env = env;
}
[BindProperty]
public SupplyLists SupplyLists { get; set; }
[BindProperty]
public FileUpload FileUpload { get; set; }
public async Task<IActionResult> OnGetAsync(int? id)
{
if (id == null)
{
return NotFound();
}
SupplyLists = await _context.SupplyLists
.Include(s => s.Category).FirstOrDefaultAsync(m => m.Id == id);
if (SupplyLists == null)
{
return NotFound();
}
ViewData["CategoryId"] = new SelectList(_context.SupplyListCategory, "Id", "Category");
return Page();
}
public async Task<IActionResult> OnPostAsync()
{
//if (!ModelState.IsValid)
//{
// return Page();
//}
_context.Attach(SupplyLists).State = EntityState.Modified;
await _context.SaveChangesAsync();
if (FileUpload.UploadSupplyList != null)
{
var fileUploadData = await utilities.utilities.ProcessFormFile(FileUpload.UploadSupplyList, ModelState);
if (ModelState.ErrorCount > 0)
{
ViewData["CategoryId"] = new SelectList(_context.SupplyListCategory, "Id", "Category");
return Page();
}
var sl = _context.SupplyLists.Find(SupplyLists.Id);
sl.ListBin = fileUploadData;
await _context.SaveChangesAsync();
}
return RedirectToPage("./Index");
}
It set the ListBin to null in db which is not what I wanted when saving changes (I wanted to preserve the old value of ListBin in db).