I tried to use {DisableValidation] both in individual Actions in the Controller and on the entire Controller but I still get Abp.Runtime.Validation.AbpValidationException: Method arguments are not valid
If I add
Configuration.Modules.AbpMvc().IsValidationEnabledForControllers = false;
the MVC validation works however, it means that I have disabled validation for all of the Abp Controllers as well which I don't want to do (things like creating Users/Roles/Tenants etc.)
Thanks. I tried setting this up with both the Default DbContent and separate DBContent but have not been able to get it to work.
My Entity class inherits IMustHaveTenant
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using System.Data.Entity.Spatial;
using Abp.Domain.Entities;
[Table("idb_IncidentDamage")]
public partial class IncidentDamage : IMustHaveTenant
{
public Guid Id { get; set; }
My Second DbContext inherits AbpDbContext
using System.Data.Entity; using OE_Tenant.Incidents.Entity; using Abp.EntityFramework;
namespace OE_Tenant.EntityFramework { public class IDBDbContext: AbpDbContext { public IDBDbContext() : base("SecondDefault")
I used the EF6 Scaffolding Wizard to generate the Controllers and Views for CRUD operations straight from the Entities and added the necessary references to Abp and inherited from the ControllerBase
When looking at breakpoints in the Controller for the AbpSession.TenantId the correct TenantId <ins>is</ins> in the session variable however, there is no database filtering taking place. All records are shown from all Tenants. There must be something that is missing/needs to be added to the Scaffolded Controller to include the TenantId in filtering and CRUD operations.
Controller using System; using System.Data.Entity; using System.Linq; using System.Net; using System.Web.Mvc; using Abp.Authorization; using OE_Tenant.EntityFramework; using OE_Tenant.Incidents.Entity; using OE_Tenant.Web.Controllers;
namespace OE_Tenant.Web.Areas.Incidents.Controllers { [AbpAuthorize] public class IncidentDamageController : OE_TenantControllerBase //: Controller { private IDBDbContext db = new IDBDbContext();
// GET: Incidents/IncidentDamage
public ActionResult Index()
{
var tenantId = AbpSession.TenantId;
var IncidentDamage = db.IncidentDamage.Include(i => i.Incident).Include(i => i.LkpDamageSeverity).Include(i => i.LkpDamageType);
return View(IncidentDamage.ToList());
}
This is the full controller generated by EF
using System; using System.Collections.Generic; using System.Data; using System.Data.Entity; using System.Linq; using System.Threading.Tasks; using System.Net; using System.Web; using System.Web.Mvc; using Abp.Authorization; using OE_Tenant.EntityFramework; using OE_Tenant.Incidents.Entity; using OE_Tenant.Web.Controllers;
namespace OE_Tenant.Web.Areas.Incidents.Controllers { [AbpAuthorize] public class IncidentDamageController : OE_TenantControllerBase //: Controller { private IDBDbContext db = new IDBDbContext();
// GET: Incidents/IncidentDamage
public ActionResult Index()
{
var tenantId = AbpSession.TenantId;
var IncidentDamage = db.IncidentDamage.Include(i => i.Incident).Include(i => i.LkpDamageSeverity).Include(i => i.LkpDamageType);
return View(IncidentDamage.ToList());
}
// GET: Incidents/IncidentDamage/Details/5
public ActionResult Details(Guid? id)
{
if (id == null)
{
return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
}
IncidentDamage IncidentDamage = db.IncidentDamage.Find(id);
if (IncidentDamage == null)
{
return HttpNotFound();
}
return View(IncidentDamage);
}
// GET: Incidents/IncidentDamage/Create
public ActionResult Create()
{
ViewBag.IncidentId = new SelectList(db.Incident, "Id", "IncidentEvent");
ViewBag.DamageSeverityId = new SelectList(db.LkpDamageSeverity, "Id", "DamageSeverity");
ViewBag.DamageTypeId = new SelectList(db.LkpDamageType, "Id", "DamageType");
return View();
}
// POST: Incidents/IncidentDamage/Create
// To protect from overposting attacks, please enable the specific properties you want to bind to, for
// more details see <a class="postlink" href="http://go.microsoft.com/fwlink/?LinkId=317598">http://go.microsoft.com/fwlink/?LinkId=317598</a>.
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create([Bind(Include = "Id,IncidentId,DamageTypeId,DamageDescription,DamageSeverityId,DamageAmount,IndexIdentity,TenantId,IsDeleted,DeletedUserId,DeletionTime,LastModificationTime,LastModifiedUserId,CreationTime,CreatorUserId")] IncidentDamage IncidentDamage)
{
if (ModelState.IsValid)
{
IncidentDamage.Id = Guid.NewGuid();
db.IncidentDamage.Add(IncidentDamage);
db.SaveChanges();
return RedirectToAction("Index");
}
ViewBag.IncidentId = new SelectList(db.Incident, "Id", "Activity", IncidentDamage.IncidentId);
ViewBag.DamageSeverityId = new SelectList(db.LkpDamageSeverity, "Id", "DamageSeverity", IncidentDamage.DamageSeverityId);
ViewBag.DamageTypeId = new SelectList(db.LkpDamageType, "Id", "DamageType", IncidentDamage.DamageTypeId);
return View(IncidentDamage);
}
// GET: Incidents/IncidentDamage/Edit/5
public ActionResult Edit(Guid? id)
{
if (id == null)
{
return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
}
IncidentDamage IncidentDamage = db.IncidentDamage.Find(id);
if (IncidentDamage == null)
{
return HttpNotFound();
}
ViewBag.IncidentId = new SelectList(db.Incident, "Id", "Activity", IncidentDamage.IncidentId);
ViewBag.DamageSeverityId = new SelectList(db.LkpDamageSeverity, "Id", "DamageSeverity", IncidentDamage.DamageSeverityId);
ViewBag.DamageTypeId = new SelectList(db.LkpDamageType, "Id", "DamageType", IncidentDamage.DamageTypeId);
return View(IncidentDamage);
}
// POST: Incidents/IncidentDamage/Edit/5
// To protect from overposting attacks, please enable the specific properties you want to bind to, for
// more details see <a class="postlink" href="http://go.microsoft.com/fwlink/?LinkId=317598">http://go.microsoft.com/fwlink/?LinkId=317598</a>.
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Edit([Bind(Include = "Id,IncidentId,DamageTypeId,DamageDescription,DamageSeverityId,DamageAmount,IndexIdentity,TenantId,IsDeleted,DeletedUserId,DeletionTime,LastModificationTime,LastModifiedUserId,CreationTime,CreatorUserId")] IncidentDamage IncidentDamage)
{
if (ModelState.IsValid)
{
db.Entry(IncidentDamage).State = EntityState.Modified;
db.SaveChanges();
return RedirectToAction("Index");
}
ViewBag.IncidentId = new SelectList(db.Incident, "Id", "Activity", IncidentDamage.IncidentId);
ViewBag.DamageSeverityId = new SelectList(db.LkpDamageSeverity, "Id", "DamageSeverity", IncidentDamage.DamageSeverityId);
ViewBag.DamageTypeId = new SelectList(db.LkpDamageType, "Id", "DamageType", IncidentDamage.DamageTypeId);
return View(IncidentDamage);
}
// GET: Incidents/IncidentDamage/Delete/5
public ActionResult Delete(Guid? id)
{
if (id == null)
{
return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
}
IncidentDamage IncidentDamage = db.IncidentDamage.Find(id);
if (IncidentDamage == null)
{
return HttpNotFound();
}
return View(IncidentDamage);
}
// POST: Incidents/IncidentDamage/Delete/5
[HttpPost, ActionName("Delete")]
[ValidateAntiForgeryToken]
public ActionResult DeleteConfirmed(Guid id)
{
IncidentDamage IncidentDamage = db.IncidentDamage.Find(id);
db.IncidentDamage.Remove(IncidentDamage);
db.SaveChanges();
return RedirectToAction("Index");
}
protected override void Dispose(bool disposing)
{
if (disposing)
{
db.Dispose();
}
base.Dispose(disposing);
}
}
}
Is there any way to use EF Scaffolding to generate CRUD operations in controllers that include TenantId for CRUD and filtering <ins>directly</ins>from the Entity without using Dtos, etc.
Thanks. Here are the features I am looking for with Subscription Management for Tenants
For example, the Tenant purchased a 5 User License so the ActiveUserCount = 5. The Admin can add 10 users and set 5 as active. Only those 5 can login. In order to allow the 6th user to login, the Admin must make one of the 5 currently active users inactive and then the 6th user can be set as active. This allows for flexibility in the license. Also it allows for more streamlined user provisioning since the users can all be added at the same time and then easily turned on or off through an editable grid interface.
A Notification System would be connected to the SubscriptionEndDate to send an email reminder to the Tenant Admin when the Subscription was going to expire (ex. 30 days out from the SubscriptionEndDate)
The SuperAdmin Management Interface would include an editable grid showing all Tenants with SubscriptionStartDate, SubscriptionEndDate, ActiveUserCount and CountofUsersCurrentlyActive
For 2, 3 and 4 if Subscription was set as implemented, the Create Tenant Form would include SubscriptionStartDate, SubscriptionEndDate and ActiveUserCount. These would typically <ins>only</ins> be used by the SuperAdmin in the initial provisioning of a new Tenant and would not be accessible to the individual Tenant Admins.
Let me know if this adequately explains how I would need Subscription Management implemented.
There are dozens of child tables so I've simplified the model to show a short version the main parent table INCIDENT and just one child table - COMMUNICATIONLOG. TenantId is saved in the Incidents Table and also is saved in all the child tables. Having TenantId in all child tables provides row level security.
INCIDENTS namespace OE_Tenant.Web.Areas.Incidents.Models { using System; using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; using System.Data.Entity.Spatial;
[Table("idb_Incident")]
public partial class Incident
{
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2214:DoNotCallOverridableMethodsInConstructors")]
public Incident()
{
CommunicationLog = new HashSet<CommunicationLog>();
}
[Key]
public Guid IncidentId { get; set; }
[UIHint("GridForeignKey")]
public int IncidentCategoryId { get; set; }
[Required]
[Column(TypeName = "smalldatetime")]
public DateTime? IncidentDate { get; set; }
[Required]
[StringLength(200)]
public string IncidentEvent { get; set; }
[UIHint("GridForeignKey")]
public int IncidentTypeId { get; set; }
[Column(TypeName = "datetime2")]
public DateTime DateEntered { get; set; }
[Column(TypeName = "datetime2")]
public DateTime? DateUpdated { get; set; }
public int TenantId { get; set; }
public virtual LkpIncidentCategory LkpIncidentCategory { get; set; }
public virtual LkpIncidentType LkpIncidentType { get; set; }
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")]
public virtual ICollection<CommunicationLog> CommunicationLog { get; set; }
}
}
COMMUNICATIONLOG
using System.ComponentModel;
namespace OE_Tenant.Web.Areas.Incidents.Models
{ using System; using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; using System.Data.Entity.Spatial;
[Table("idb_CommunicationLog")]
public partial class CommunicationLog
{
[Key]
public Guid CommunicationLogId { get; set; }
[DisplayName("Communication Type")]
[UIHint("GridForeignKey")]
public int CommunicationTypeId { get; set; }
[DisplayName("Direction")]
[UIHint("GridForeignKey")]
public int CommunicationDirectionId { get; set; }
public Guid IncidentId { get; set; }
[DataType(DataType.Date)]
[DisplayFormat(ApplyFormatInEditMode = true, DataFormatString = "{0:MM/dd/yyyy}")]
public DateTime? Date { get; set; }
[DataType(DataType.Time)]
[DisplayFormat(ApplyFormatInEditMode = true, DataFormatString = "{0:t}")]
public TimeSpan? StartTime { get; set; }
[DataType(DataType.Time)]
[DisplayFormat(ApplyFormatInEditMode = true, DataFormatString = "{0:t}")]
public TimeSpan? EndTime { get; set; }
[StringLength(50)]
public string FirstName { get; set; }
[StringLength(50)]
[Display(Name = "Last Name", Prompt = "Enter person's last name")]
public string LastName { get; set; }
[StringLength(200)]
public string Address1 { get; set; }
[StringLength(100)]
public string CountryCode { get; set; }
[Column(TypeName = "datetime2")]
public DateTime DateEntered { get; set; }
[Column(TypeName = "datetime2")]
public DateTime DateUpdated { get; set; }
public int TenantId { get; set; }
public virtual LkpCommunicationDirection LkpCommunicationDirection { get; set; }
public virtual LkpCommunicationType LkpCommunicationType { get; set; }
public virtual Incident Incident { get; set; }
public virtual LkpCountry LkpCountry { get; set; }
}
}