Hi ramilcatalandomingo, You could use SeedHelper in project EntityFrameworkCore to plug in your own class: Your DefaultTenantDataBuilder would look as follows:
using Nuagecare.EntityFrameworkCore;
namespace Nuagecare.Migrations.Seed.Tenants
{
public class DefaultTenantDataBuilder
{
private readonly NuagecareDbContext _context;
private readonly int _tenantId;
public DefaultTenantDataBuilder(
NuagecareDbContext context,
int tenantId)
{
_context = context;
_tenantId = tenantId;
}
public void Create()
{
new InitialOrganizationUnitCreator(_context, _tenantId).Create();
new InitialNcFormCreator(_context, _tenantId).Create();
_context.SaveChanges();
new InitialNcEntityCreator(_context, _tenantId).Create();
new InitialDeviceCreator(_context, _tenantId).Create();
_context.SaveChanges();
}
public void CreateChildTables(int tenantId)
{
//Nuagecare InitialDataCreators
new InitialNcActionFirstLevelCreator(_context, tenantId).Create();
_context.SaveChanges();
}
}
}
And then your InitialOrganizationUnitCreator something like this:
using Abp.Dependency;
using Abp.Domain.Uow;
using Abp.Organizations;
using Microsoft.EntityFrameworkCore;
using Nuagecare.EntityFrameworkCore;
using System.Linq;
namespace Nuagecare.Migrations.Seed.Tenants
{
public class InitialOrganizationUnitCreator
{
private readonly NuagecareDbContext _context;
private readonly int _tenantId;
public InitialOrganizationUnitCreator(
NuagecareDbContext context,
int tenantId)
{
_context = context;
_tenantId = tenantId;
}
public void Create()
{
var root = _context.OrganizationUnits.IgnoreQueryFilters().FirstOrDefault(m => m.TenantId == _tenantId && m.DisplayName == "Acme Care Home");
if (root == null)
{
using (var organizationUnitManager = IocManager.Instance.ResolveAsDisposable<OrganizationUnitManager>())
{
organizationUnitManager.Object.Create(new OrganizationUnit(_tenantId, "Acme Care Home", null));
}
}
_context.SaveChanges();
root = _context.OrganizationUnits.IgnoreQueryFilters().FirstOrDefault(m => m.TenantId == _tenantId && m.DisplayName == "Acme Care Home");
using (var scope = IocManager.Instance.CreateScope())
{
var organizationUnitManager = scope.Resolve<OrganizationUnitManager>();
var unitOfWorkManager = scope.Resolve<IUnitOfWorkManager>();
using (unitOfWorkManager.Current.DisableFilter(AbpDataFilters.MayHaveTenant))
{
var admissions = _context.OrganizationUnits.IgnoreQueryFilters().FirstOrDefault(m => m.TenantId == _tenantId && m.DisplayName == "Admissions");
if (admissions == null)
{
organizationUnitManager.Create(new OrganizationUnit(_tenantId, "Admissions", root.Id));
}
var firstFloor = _context.OrganizationUnits.IgnoreQueryFilters().FirstOrDefault(m => m.TenantId == _tenantId && m.DisplayName == "First Floor");
if (firstFloor == null)
{
organizationUnitManager.Create(new OrganizationUnit(_tenantId, "First Floor", root.Id));
}
var secondFloor = _context.OrganizationUnits.IgnoreQueryFilters().FirstOrDefault(m => m.TenantId == _tenantId && m.DisplayName == "Second Floor");
if (secondFloor == null)
{
organizationUnitManager.Create(new OrganizationUnit(_tenantId, "Second Floor", root.Id));
}
}
_context.SaveChanges();
}
}
}
}
Hope that helps.
Yes. Me. If you find an answer please do let me know!
@demirmusa, brilliant, exactly what I was looking for. It also looks like it may get rid of the CultureUI warnings in the log files. As an aside, why isn't Zero on 4.7 as a matter of course?
Hi @demirmusa, Thanks for the link. I will test that out using one of my deployment slots.
Can I upgrade my .NET framework to 4.7.1 without causing any breaking changes to .NET Zero? Are there any known issues?
Please tell me how to get rid of these ridiculous messages in the log:
WARN 2019-11-24 13:03:22,649 [79 ] calization.RequestLocalizationMiddleware - AbpLocalizationHeaderRequestCultureProvider returned the following unsupported cultures 'null'.
WARN 2019-11-24 13:03:22,649 [79 ] calization.RequestLocalizationMiddleware - AbpLocalizationHeaderRequestCultureProvider returned the following unsupported UI Cultures 'null'.
If it's not a problem don't throw a warning, throw an info statement. I am about to hand over my infrastructure to a third party and the first thing they want to see are logs. Trying to sift through hundreds of failed logins because the user can't remember their username or password is bad enough, it's hard to find real exceptions in the Zero logs because of the crap that's in there. But then trying to explain that a warning is not really a warning it's just something that can be safely ignored really doesn't cut it when you're trying to prove that the system is ready for handover. I need to be made aware of exceptions and warnings by my infrastructure partner. They need to be assured that the system is supportable and it doesn't help when there's a lot of crap in there which needs to be filtered out.
@peopleteq, my approach was to copy the Zero AppTokenAuthController and place it in the Web.Core project under a different namespace. I then have the standard sign in for the Zero system and a different sign in route for my Ionic apps, that way I can control device registration: Just be careful to keep an eye on github for changes to the standard Zero AppTokenAuthController so you can implement any changes in your own AppTokenAuthController. This route has worked for me to date and I've been running a trial production system for over a year.
Hi @demirmusa, We are considering handing over our Zero development to a third party. We are currently talking to three Zero partners much bigger than us who can deal with the demand. Alper is helping us with this task, everything is completely transparent. How do I get my connection strings and authorizations out of the config files so I can pass along source code without giving access to my prospective partner? It's more a business question than a coding question, maybe Ishmael or Alper can help you out. BTW - I really appreciate the work you have done on Zero recently, I'm on an earlier version (6.8.0) but your current stuff is brilliant.
Oooooh, you must be a real techie. I had to make do with Cobol.
Hi @mitch, I'm getting too old for this game, I usually document everything but I can see from my notes that this was a difficult process of trial and error. Here's my notes:
In project Nuagecare.Web.Host add the following Nuget packages from the “Telerik Nuget” package course: • Telerik.Reporting.Services.AspNetCore • Telerik.Reporting Add new controller, KendoReportsController to Nuagecare.Web.Host following the instructions at: • https://docs.telerik.com/reporting/html5-report-viewer-asp-net-core-2#setting-up-the-rest-service and • https://www.telerik.com/blogs/telerik-reporting-and-aspnet-core And the code for my controller is below.
using Abp.Authorization;
using Abp.Dependency;
using Abp.Web.Models;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Configuration;
using System;
using System.IO;
using Telerik.Reporting;
using Telerik.Reporting.Cache.File;
using Telerik.Reporting.Services;
using Telerik.Reporting.Services.AspNetCore;
using Telerik.Reporting.Services.Engine;
namespace Nuagecare.Web.Host.Controllers
{
[ApiExplorerSettings(IgnoreApi = true)]
[AbpAuthorize]
[DontWrapResult]
public class KendoReportsController : ReportsControllerBase, ITransientDependency
{
public KendoReportsController(IHostingEnvironment environment)
{
var reportsPath = Path.Combine(environment.WebRootPath, "Reports");
try
{
this.ReportServiceConfiguration = new ReportServiceConfiguration
{
HostAppId = "Nuagecare",
Storage = new FileStorage(),
ReportResolver = new NuageResolver(reportsPath),
ReportSharingTimeout = 0,
ClientSessionTimeout = 15,
};
}
catch (System.Exception ex)
{
throw ex;
}
}
}
//https://www.telerik.com/support/kb/reporting/details/changing-the-connection-string-dynamically-according-to-runtime-data
//https://www.telerik.com/forums/change-connectionstring-in-runtime as an alternative
public class NuageResolver : IReportResolver
{
public string repositoryDirectory { get; set; }
public NuageResolver(string repositoryDirectory)
{
this.repositoryDirectory = repositoryDirectory;
}
//this method will be called on each request for report (refresh, navigate to report, sub report, parameters updates)
//the method will be called 3 times on initial load
public ReportSource Resolve(string report)
{
var connectionStringHandler = new ReportConnectionStringManager(GetConnectionString());
var sourceReportSource = new UriReportSource { Uri = this.repositoryDirectory + "/" + report };
var reportSource = connectionStringHandler.UpdateReportSource(sourceReportSource);
return reportSource;
}
private string GetConnectionString()
{
var env = AppDomain.CurrentDomain.GetData("HostingEnvironment") as IHostingEnvironment;
string currentDir = Directory.GetCurrentDirectory();
var builder = new ConfigurationBuilder()
.SetBasePath(currentDir)
.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true)
.AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true);
var configuration = builder.Build();
string connectionString = configuration.GetConnectionString("Default");
return connectionString;
}
}
//https://www.telerik.com/support/kb/reporting/details/changing-the-connection-string-dynamically-according-to-runtime-data
public class ReportConnectionStringManager
{
readonly string connectionString;
public ReportConnectionStringManager(string connectionString)
{
this.connectionString = connectionString;
}
public ReportSource UpdateReportSource(ReportSource sourceReportSource)
{
if (sourceReportSource is UriReportSource)
{
var uriReportSource = (UriReportSource)sourceReportSource;
// unpackage TRDP report
// http://docs.telerik.com/reporting/report-packaging-trdp#unpackaging
var reportInstance = UnpackageReport(uriReportSource);
// or deserialize TRDX report(legacy format)
// http://docs.telerik.com/reporting/programmatic-xml-serialization#deserialize-report-definition-from-xml-file
// var reportInstance = DeserializeReport(uriReportSource);
ValidateReportSource(uriReportSource.Uri);
this.SetConnectionString(reportInstance);
return CreateInstanceReportSource(reportInstance, uriReportSource);
}
if (sourceReportSource is XmlReportSource)
{
var xml = (XmlReportSource)sourceReportSource;
ValidateReportSource(xml.Xml);
var reportInstance = this.DeserializeReport(xml);
this.SetConnectionString(reportInstance);
return CreateInstanceReportSource(reportInstance, xml);
}
if (sourceReportSource is InstanceReportSource)
{
var instanceReportSource = (InstanceReportSource)sourceReportSource;
this.SetConnectionString((ReportItemBase)instanceReportSource.ReportDocument);
return instanceReportSource;
}
if (sourceReportSource is TypeReportSource)
{
var typeReportSource = (TypeReportSource)sourceReportSource;
var typeName = typeReportSource.TypeName;
ValidateReportSource(typeName);
var reportType = Type.GetType(typeName);
var reportInstance = (Report)Activator.CreateInstance(reportType);
this.SetConnectionString((ReportItemBase)reportInstance);
return CreateInstanceReportSource(reportInstance, typeReportSource);
}
throw new NotImplementedException("Handler for the used ReportSource type is not implemented.");
}
ReportSource CreateInstanceReportSource(IReportDocument report, ReportSource originalReportSource)
{
var instanceReportSource = new InstanceReportSource { ReportDocument = report };
instanceReportSource.Parameters.AddRange(originalReportSource.Parameters);
return instanceReportSource;
}
void ValidateReportSource(string value)
{
if (value.Trim().StartsWith("="))
{
throw new InvalidOperationException("Expressions for ReportSource are not supported when changing the connection string dynamically");
}
}
Report UnpackageReport(UriReportSource uriReportSource)
{
var reportPackager = new ReportPackager();
using (var sourceStream = System.IO.File.OpenRead(uriReportSource.Uri))
{
var report = (Report)reportPackager.UnpackageDocument(sourceStream);
return report;
}
}
Report DeserializeReport(UriReportSource uriReportSource)
{
var settings = new System.Xml.XmlReaderSettings();
settings.IgnoreWhitespace = true;
using (var xmlReader = System.Xml.XmlReader.Create(uriReportSource.Uri, settings))
{
var xmlSerializer = new Telerik.Reporting.XmlSerialization.ReportXmlSerializer();
var report = (Telerik.Reporting.Report)xmlSerializer.Deserialize(xmlReader);
return report;
}
}
Report DeserializeReport(XmlReportSource xmlReportSource)
{
var settings = new System.Xml.XmlReaderSettings();
settings.IgnoreWhitespace = true;
var textReader = new System.IO.StringReader(xmlReportSource.Xml);
using (var xmlReader = System.Xml.XmlReader.Create(textReader, settings))
{
var xmlSerializer = new Telerik.Reporting.XmlSerialization.ReportXmlSerializer();
var report = (Telerik.Reporting.Report)xmlSerializer.Deserialize(xmlReader);
return report;
}
}
void SetConnectionString(ReportItemBase reportItemBase)
{
if (reportItemBase.Items.Count < 1)
return;
if (reportItemBase is Report)
{
var report = (Report)reportItemBase;
if (report.DataSource is SqlDataSource)
{
var sqlDataSource = (SqlDataSource)report.DataSource;
sqlDataSource.ConnectionString = connectionString;
}
foreach (var parameter in report.ReportParameters)
{
if (parameter.AvailableValues.DataSource is SqlDataSource)
{
var sqlDataSource = (SqlDataSource)parameter.AvailableValues.DataSource;
sqlDataSource.ConnectionString = connectionString;
}
}
}
foreach (var item in reportItemBase.Items)
{
//recursively set the connection string to the items from the Items collection
SetConnectionString(item);
if (item is SubReport)
{
var subReport = (SubReport)item;
subReport.ReportSource = this.UpdateReportSource(subReport.ReportSource);
continue;
}
//Covers all data items(Crosstab, Table, List, Graph, Map and Chart)
if (item is DataItem)
{
var dataItem = (DataItem)item;
if (dataItem.DataSource is SqlDataSource)
{
var sqlDataSource = (SqlDataSource)dataItem.DataSource;
sqlDataSource.ConnectionString = connectionString;
continue;
}
}
}
}
}
}
My angular front end is of no use to you but hopefully you should get enough from the commented links to help you. Btw - I have noticed that Kendo Reports creates spikes in CPU demand, it's a hungry beast. Recently I have been toying with the idea of moving the above code into a separate web app in order to abstract it away from the main application. Let me know how you get on. Oh, and my app.config file:
<configuration>
<configSections>
</configSections>
<connectionStrings>
<add name="Nuagecare.Reports.Properties.Settings.Nuagecare" connectionString="Data Source=.;Initial Catalog=Nuagecare;Integrated Security=True"
providerName="System.Data.SqlClient" />
</connectionStrings>
<appSettings>
<add key="owin:AutomaticAppStartup" value="false"/>
</appSettings>
<runtime>
<gcServer enabled="true"/>
</runtime>
</configuration>
Just a simple way of getting connection strings out of config files would sort me....