Closed FDEC-KKelley closed 3 years ago
Hey @FDEC-KKelley
Sorry for late answer. The code is out of date for multiscopes. Sorry about that. I need to improve the documentation to fit the last version...
You can set your scope on the SyncAgent
itself, instead of SyncSetup
:
var agent = new SyncAgent(clientProvider, serverProvider, options, setup, "Scope1");
For the space in the table name, I will do more tests on that, thanks !
Thank you for the reply, I'll code the change next week. It's a great syncing framework you've built.
I've tried your suggestion of putting the scope name on the SyncAgent and have found the folllowing.
var agent = new SyncAgent(clientProvider, serverProvider, options, setup, "Scope1");
If anything other than "DefaultScope" is specified in the above example for the scope name the following two errors are thrown and sync fails:
Exception thrown: 'Dotmim.Sync.Web.Client.HttpSyncWebException' in Dotmim.Sync.Web.Client.dll Exception thrown: 'Dotmim.Sync.Web.Client.HttpSyncWebException' in Dotmim.Sync.Web.Client.dll
Can you describe your configuration, from the server side ? Can you get more info from the error raised on the server side ?
An overview of my configuration:
The error on the server side when trying to use a named scope other than DefaultScope:
Exception:
Dotmim.Sync.HttpScopeNameInvalidException: The scope Scope1 does not exist on the server side. Please provider a correct scope name
at Dotmim.Sync.Web.Server.WebServerManager.GetOrchestrator(String scopeName)
at Dotmim.Sync.Web.Server.WebServerManager.GetOrchestrator(HttpContext context)
at signalr.Hubs.SyncController.Post(String dbn)
at signalr.Hubs.SyncController.TryAsync(String dbNameIn)
at lambda_method6(Closure , Object )
at Microsoft.AspNetCore.Mvc.Infrastructure.ActionMethodExecutor.AwaitableResultExecutor.Execute(IActionResultTypeMapper mapper, ObjectMethodExecutor executor, Object controller, Object[] arguments)
at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.
A simple database with 7 tables and one scope on the server that I'm trying to sync with named scope "Scope1"
I'll add some more debug logging to my server side code to see if I can get anymore detail. As stated before if I use the "DefaultScope" name no error is thrown. My goal is to sync using multiple scope names with each containing a certain number of tables.
On your server side, did you add correctly the provider with the scope ?
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers();
// [Required]: Handling multiple sessions
services.AddMemoryCache();
// [Required]: Get a connection string to your server data source
var connectionString = Configuration.GetSection("ConnectionStrings")["SqlConnection"];
// var connectionString = Configuration.GetSection("ConnectionStrings")["MySqlConnection"];
// [Required] Tables involved in the sync process:
var tables = new string[] {"ProductCategory", "ProductModel", "Product",
"Address", "Customer", "CustomerAddress", "SalesOrderHeader", "SalesOrderDetail" };
// [Required]: Add a SqlSyncProvider acting as the server hub.
services.AddSyncServer<SqlSyncChangeTrackingProvider>(connectionString, "Scope1", tables);
//services.AddSyncServer<MySqlSyncProvider>(connectionString, tables);
}
This doesn't seem to allow the scope name to be changed once the service is loaded. When trying to change to the next scope name "Scope2" it throws this error:
System.InvalidOperationException: The response headers cannot be modified because the response has already started.
I can't find anyway to get around this problem.
I can change the server side tables to sync in HttpPost without error using:
var orchestrator = webServerManager.GetOrchestrator(this.HttpContext);
var setup = new SyncSetup(strTableNames );
orchestrator.Setup.Tables.Clear(); //clear table list
orchestrator.Setup.Tables.AddRange(tables); //add table list for current scope name
If there were a similar method for scope name I think it would work.
public static string Scopename { get;set; }
public void ConfigureServices(IServiceCollection services)
{
// [Required]: Handling multiple sessions
services.AddMemoryCache();
services.AddAuthorization();
services.AddCors(options =>
{
options.AddPolicy(MyAllowSpecificOrigins,
builder =>
{
builder.WithOrigins("https://localhost:44369")
.AllowAnyHeader()
.AllowAnyMethod()
.AllowCredentials();
});
});
// SignalR hub service for streaming server side sync messages to client app
services.AddSignalR().AddHubOptions<SyncHub>(options =>
{
options.EnableDetailedErrors = true;
});
services.AddControllers();
// connection string to server data source
var connectionString = Configuration.GetSection("ConnectionStrings")["SqlConnection"];
var tables = new string[] { "[tblVersionControl]" }; //inital table to sync
if(string.IsNullOrEmpty(Scopename) == true)
{
Scopename = "Scope1"; //default first scope name
}
// SqlSyncProvider acting as the server hub.
services.AddSyncServer<SqlSyncProvider>(connectionString, Scopename, tables);
}
[HttpPost("db")]
public async Task TryAsync(string dbNameIn = "") //get database name from api url and pass to Post method
{
string dbNameInFormat = dbNameIn.Remove(dbNameIn.Length - 1, 1);
await Post(dbNameInFormat);
}
public async Task Post(string dbn)
{
WebSyncProxy.Info("SyncController: Post: Database Name: " + dbn.ToString()); //debug logging
connectionPart2 = dbn;
connectionFinal = string.Concat(connectionPart1, connectionPart2, connectionPart3); //finalize connection string based on passed in string dbn (database name)
DataSet scopeNamesDataSet = new DataSet();
string scopeNameHold = string.Empty;
string currentScopeName = string.Empty;
string forqrueryString = string.Empty;
var connection = new SqlConnection(connectionFinal);
//Query that gets the scopes to create
forqrueryString = "Use " + dbn + " select distinct [ScopeName] from qryScopeNames";
SqlDataAdapter scopeNameAdapter = new SqlDataAdapter();
scopeNameAdapter.SelectCommand = new SqlCommand(forqrueryString, connection);
scopeNameAdapter.Fill(scopeNamesDataSet);
foreach (DataTable scopeNames in scopeNamesDataSet.Tables) //loop through scope names
{
foreach (DataRow row in scopeNames.Rows)
{
currentScopeName = (@row[0].ToString());
Startup.Scopename = currentScopeName; //update service scopename
GetTables(dbn, currentScopeName, out string tablesList); //get list of sync tables based on passed in scope name
string[] strTableNames = new string[] { "" };
strTableNames = tablesList.Split(',');
var tables = strTableNames ;
var serverProvider = new SqlSyncProvider(connection.ToString().Replace("Master", dbn));
var setup = new SyncSetup(strTableNames );
var options = new SyncOptions();
options.UseVerboseErrors = true;
options.UseBulkOperations = false;
options.CleanFolder = false;
options.DisableConstraintsOnApplyChanges = true;
options.CleanMetadatas = false;
var orchestrator = webServerManager.GetOrchestrator(this.HttpContext);
WebSyncProxy.Info("SyncController: Post: currentScopeName: " + currentScopeName.ToString()); //debug logging
WebSyncProxy.Info("SyncController: Post: orchestratorScopeName: " + orchestrator.ScopeName.ToString());//debug logging
orchestrator.Setup.Tables.Clear(); //clear table list
orchestrator.Setup.Tables.AddRange(tables); //add table list for current scope name
WebSyncProxy.Info("SyncController: Post: Tables: " + orchestrator.Setup.Tables.ToString()); //debug logging
orchestrator.Provider.ConnectionString = connectionFinal;
try
{
var progress = new SynchronousProgress<ProgressArgs>(pa =>
syncmessage = ($"<Server> Sync Stage>> " + pa.Context.SyncStage + "\t Sync Message>> " + pa.Message));
// handle request
await webServerManager.HandleRequestAsync(this.HttpContext, default, progress);
}
catch (SqlException se)
{
WebSyncProxy.Error("SyncController: Post: " + se.ToString());
await WebServerManager.WriteExceptionAsync(this.HttpContext.Response, se);
}
catch (SyncException e)
{
WebSyncProxy.Error("SyncController: Post: " + e.ToString());
await WebServerManager.WriteExceptionAsync(this.HttpContext.Response, e);
}
catch (Exception ex)
{
WebSyncProxy.Error("SyncController: Post: " + ex.ToString());
await WebServerManager.WriteExceptionAsync(this.HttpContext.Response, ex);
}
}
}
}
I dont understand what you are doing;, actually.
The only things I can say about that is:
Add one provider for each scope you have to handle:
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers();
services.AddMemoryCache();
var connectionString = Configuration.GetSection("ConnectionStrings")["SqlConnection"];
var tables = new string[] {"ProductCategory", "ProductModel", "Product"};
services.AddSyncServer<SqlSyncChangeTrackingProvider>(connectionString, "Scope_Products", tables);
var tables2 = new string[] { "Address", "Customer", "CustomerAddress"};
services.AddSyncServer<SqlSyncChangeTrackingProvider>(connectionString, "Scope_Customers", tables2);
var tables3 = new string[] { "SalesOrderHeader", "SalesOrderDetail" };
services.AddSyncServer<SqlSyncChangeTrackingProvider>(connectionString, "Scope_Sales", tables3);
}
From the client do not forget to add the scope name you want to reach. Client needs to know which scope he needs to reach.
Do not change your setup tables in your POST
Just don't do that:
var orchestrator = webServerManager.GetOrchestrator(this.HttpContext);
var setup = new SyncSetup(strTableNames );
orchestrator.Setup.Tables.Clear(); //clear table list
orchestrator.Setup.Tables.AddRange(tables); //add table list for current scope name
If you have multiple scopes (as I did in the previous code example) you won't have to do that
If there were only one defined database schema to sync I agree your example is the way to go. However, I am faced with several different databases with varying tables. Sorry I didn't state that in my original post. Hard coding table names won't work for me.
I'm afraid your scenario is not supported by the DMS framework. Did you find a way to make it work, finally ?
Yes I was able to get it working. Below is partial code from my desktop application main form, along with my API startup and controller code.
// ------------------------------------------------------------------------------------
// partial mainform.cs loops through each scope to sync for the user selected database
// ------------------------------------------------------------------------------------
public async Task < bool > SyncThroughWebApiAsync(CancellationToken cancellationToken, string DatabaseToSync) {
// ------------------------------------------------------------------------------------
// Get list of scope names for the selected database
// ------------------------------------------------------------------------------------
await GetScopesAsync(DatabaseToSync, "%", true);
string[] strScopes = new string[] {
""
};
strScopes = tableList.Split(',');
var scopes = strScopes;
// ------------------------------------------------------------------------------------
// loop through all defined scopes for selected database
// ------------------------------------------------------------------------------------
foreach(string strScopeName in scopes) {
var taskCompletionSource = new TaskCompletionSource < bool > ();
if (cancellationToken.IsCancellationRequested || Globals.SyncCancel == true) {
cancellationToken.ThrowIfCancellationRequested();
}
if (cancellationToken.IsCancellationRequested) {
return false;
}
AddItemToListBox("Syncing Project Database: >> " + DatabaseToSync);
var scopeName = strScopeName.Replace("[", "").Replace("]", "").Trim('"');
var clientProvider = new SqlSyncProvider(ConnectionFinal);
var handler = new HttpClientHandler {
AutomaticDecompression = DecompressionMethods.GZip
};
var client = new HttpClient(handler) {
Timeout = TimeSpan.FromMinutes(5)
};
// ------------------------------------------------------------------------------------
// Sync API Proxy, send database name and scope name to sync
// ------------------------------------------------------------------------------------
var proxyClientProvider = new WebClientOrchestrator(SyncDataClass.WebSyncUrl + ":44369/api/Sync/db?dbNameIn=" + DatabaseToSync + "^" + scopeName, null, null, null, 4);
//batch folder
System.IO.Directory.CreateDirectory(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData) + @ "\Temp\CACEA_Sync_Folder");
// ----------------------------------
// Client side options
// ----------------------------------
var clientOptions = new SyncOptions {
ScopeInfoTableName = "scope_info",
DisableConstraintsOnApplyChanges = true,
BatchDirectory = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData) + @ "\Temp\CACEA_Sync_Folder",
SnapshotsDirectory = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData) + @ "\Temp\CACEA_Sync_Folder",
BatchSize = 500,
CleanMetadatas = true,
UseBulkOperations = false,
UseVerboseErrors = true,
ConflictResolutionPolicy = (ConflictResolutionPolicy) ConflictResolution.ServerWins,
};
agent = new SyncAgent(clientProvider, proxyClientProvider, clientOptions, scopeName);
// Intercept a table changes selecting stage.
if (SyncDataClass.DatabaseType == "PROJECTDB" | SyncDataClass.DatabaseType == "PREFDB")
{
agent.LocalOrchestrator.OnTableChangesSelecting(args => {
if (Globals.SyncCancel == true) {
args.Cancel = true;
CancelSync();
return;
}
AddItemToListBox($"[Client]-->Getting_Changes: Getting changes from table: {args.Table.GetFullName()} ...");
});
}
try {
//Set progress output to write to listbox
var progressClient = new SynchronousProgress < ProgressArgs > (s => this.Invoke(new BindLstBoxControl(AddItemToListBox), new object[] {
$ "[Client]-->Progress: {s.Context.SyncStage}:\t{DateTime.Now.ToString("
T ")}:\t{ s.Message}"
}));
// This proxy is for getting sync status updates from the API sync proxy
// SignalR is used with a streaming hub to send updates to the desktop GUI during a sync, as the API cannot send these messages back realtime
HubConnection connection = new HubConnectionBuilder()
.WithUrl(SyncDataClass.WebSyncUrl + ":44369/syncHub", options => {
options.ClientCertificates.Add(clientCert);
})
.WithAutomaticReconnect()
.Build();
//create a cancellation token to use for the SignalR stream from the proxy
var cancellationTokenSource = new CancellationTokenSource();
await connection.StartAsync(cancellationTokenSource.Token).ContinueWith(t => {
if (t.IsFaulted)
// ToDo: add code for faulted initial connection
Debug.WriteLine(t.Exception.GetBaseException());
else
});
var s = agent.SynchronizeAsync(SyncType.Normal, cancellationTokenSource.Token, progressClient);
if (Globals.SyncCancel == true) {
cancellationTokenSource.Cancel();
CancelSync();
break;
}
//make sure listbox scrolls
lstMessages.SelectedIndex = lstMessages.Items.Count - 1;
lstMessages.SelectedIndex = -1;
//get the sync agent session state
string agentsession = agent.SessionState.ToString();
string receivedold = string.Empty;
string receivednew = string.Empty;
//get updates from the SignalR hub while syncing
while (agent.SessionState == SyncSessionState.Synchronizing) {
if (Globals.SyncCancel == true) {
cancellationTokenSource.Cancel();
CancelSync();
break;
}
Globals.SyncInProgress = true;
ProgressFormShown = true;
agentsession = agent.SessionState.ToString(); //check for session state change
var stream = connection.StreamAsync < SomeData > ("GetSomeDataWithAsyncStreams", 5, 50, cancellationTokenSource.Token);
await foreach(var d in stream) {
receivednew = d.Value ?? "";
if (receivednew.Contains(receivedold)) //keep from displaying duplicate update information
{
receivedold = d.Value ?? "";
} else {
if (d.Value.Contains("<Server>") == true) {
lstMessages.Items.Add(d.Value); //add to listbox
//make sure listbox scrolls
lstMessages.SelectedIndex = lstMessages.Items.Count - 1;
lstMessages.SelectedIndex = -1;
}
}
receivedold = d.Value ?? "";
}
}
Globals.SyncInProgress = false;
} catch (TaskCanceledException) {
guiDebug.Error("MainForm: SyncThroughWebApiAsync: " + TaskCanceledException.ToString());
} catch (SyncException e) {
Globals.SyncInProgress = false;
string messageerr = "Error During Sync of database " + DatabaseToSync + ": " + e.Message.ToString();
string title = "Sync Error";
MessageBoxButtons buttons = MessageBoxButtons.OK;
DialogResult result = MessageBox.Show(messageerr, title, buttons);
guiDebug.Error("MainForm: SyncThroughWebApiAsync: " + e.ToString());
return false;
} catch (Exception e) {
Globals.SyncInProgress = false;
string messageerr = "General Error During Sync of database " + DatabaseToSync + ": " + e.ToString();
string title = "Sync Error";
MessageBoxButtons buttons = MessageBoxButtons.OK;
DialogResult result = MessageBox.Show(messageerr, title, buttons);
guiDebug.Error("MainForm: SyncThroughWebApiAsync: " + e.ToString());
return false;
}
}
return true;
}
// ------------------------------------------------------------------------------------
// startup.cs
// ------------------------------------------------------------------------------------
using Dotmim.Sync;
using Dotmim.Sync.Web.Server;
using Microsoft.AspNetCore.Mvc;
using NLog;
using System;
using System.Collections.Generic;
using System.Data;
using System.Data.SqlClient;
using System.Diagnostics;
using System.Threading.Tasks;
namespace signalr.Hubs {
[Route("api/[controller]")]
[ApiController]
public class SyncController: ControllerBase {
private Logger WebSyncProxy = LogManager.GetLogger("WebSyncProxy");
private WebServerManager webServerManager;
string connectionPart1 { get; set; }
string connectionPart2 { get; set; }
string connectionPart3 { get; set; }
string connectionFinal { get; set; }
public SyncController(WebServerManager webServerManager) {
this.webServerManager = webServerManager;
connectionPart1 = @ "Data Source=DBserver\fdec;Initial Catalog=";
connectionPart3 = ";Trusted_Connection=True;";
}
public static string syncmessage { get; set; }
[HttpPost("db")]
// ------------------------------------------------------------------------------------
// get database name and scope name from api url and pass to Post method
// ------------------------------------------------------------------------------------
public async Task TryAsync(string dbNameIn = "") {
try {
string scopeNameInFormat = dbNameIn.Substring(dbNameIn.IndexOf("^") + 1, (dbNameIn.Length - dbNameIn.IndexOf("^")) - 2); //scope name
string dbNameInFormat = dbNameIn.Substring(0, dbNameIn.IndexOf("^")); //database name
await Post(dbNameInFormat, scopeNameInFormat); //send database name and scope name to POST method
} catch (SyncException e) {
WebSyncProxy.Error("Error** SyncController: TryAsync: e: " + e.ToString());
await WebServerManager.WriteExceptionAsync(this.HttpContext.Response, e);
}
}
// ------------------------------------------------------------------------------------
// dbn = database name to sync from client, scopeIn = scope name from client
// ------------------------------------------------------------------------------------
public async Task Post(string dbn, string scopeIn) {
connectionPart2 = dbn;
connectionFinal = string.Concat(connectionPart1, connectionPart2, connectionPart3); //finalize connection string based on passed in string dbn
var orchestrator = webServerManager.GetOrchestrator(scopeIn); // get scope orchestrator
orchestrator.Setup.Tables.Clear(); //clear default table list
orchestrator.Provider.ConnectionString = connectionFinal;
try {
WebSyncProxy.Info("Information** SyncController: Post: Orchestrator ScopeName: " + orchestrator.ScopeName.ToString());
// ------------------------------------------------------------------------------------
// get list of sync tables based on passed in string dbn
// ------------------------------------------------------------------------------------
GetTables(dbn, orchestrator.ScopeName.ToString(), out string tablesList);
string[] strTables = new string[] {
""
};
strTables = tablesList.Split(',');
var tables = strTables;
// ------------------------------------------------------------------------------------
// add tables for the current scope
// ------------------------------------------------------------------------------------
orchestrator.Setup.Tables.AddRange(tables);
var progress = new SynchronousProgress < ProgressArgs > (pa =>
syncmessage = ($"<Server> Sync Stage>> " + pa.Context.SyncStage + "\t Sync Message>> " + pa.Message));
await orchestrator.HandleRequestAsync(this.HttpContext,
default, progress); //process the current scope
} catch (SqlException se) {
WebSyncProxy.Error("Error** SyncController: Post: se: " + se.ToString());
await WebServerManager.WriteExceptionAsync(this.HttpContext.Response, se);
} catch (SyncException e) {
WebSyncProxy.Error("Error** SyncController: Post: e: " + e.ToString());
await WebServerManager.WriteExceptionAsync(this.HttpContext.Response, e);
} catch (Exception ex) {
WebSyncProxy.Error("Error** SyncController: Post: ex: " + ex.ToString());
await WebServerManager.WriteExceptionAsync(this.HttpContext.Response, ex);
}
}
[HttpGet]
[RequireHttps]
public async Task Get() => await webServerManager.HandleRequestAsync(this.HttpContext);
public string GetTables(string DbToUse, string scopeNameFilter, out string tableList) {
DataSet DS = new DataSet();
string queryString = string.Empty;
List < string > TableList = new List < string > ();
string tblList = string.Empty;
SqlConnection connection = new SqlConnection(connectionFinal);
// ------------------------------------------------------------------------------------
// Query that gets all the tables to sync
// ------------------------------------------------------------------------------------
queryString = "Use " + DbToUse + " select [TableNames],[ScopeName] from qryScopeNames where ScopeName = '" + scopeNameFilter + "' group by [TableNames],scopename ";
try {
SqlDataAdapter adapter = new SqlDataAdapter();
adapter.SelectCommand = new SqlCommand(queryString, connection);
adapter.Fill(DS);
foreach(DataTable table in DS.Tables) {
foreach(DataRow row in table.Rows)
TableList.Add(@ "[" + row[0].ToString() + @ "]"); //Surround table names with brackets encase a table name has a space in it
}
} catch (System.Data.SqlClient.SqlException exception) {
WebSyncProxy.Error("Error** SyncController: Post: " + exception.ToString());
connection.Close();
Debug.WriteLine(exception.ToString());
} catch (Exception ex) {
WebSyncProxy.Error("Error** SyncController: Post: " + ex.ToString());
connection.Close();
Debug.WriteLine(ex.ToString());
}
tblList = "\"" + string.Join("\", \"", TableList.ToArray()) + "\""; //Surround table names with double quotes
tableList = tblList;
return tableList;
}
}
}
// ------------------------------------------------------------------------------------
// SyncController.cs
// ------------------------------------------------------------------------------------
using Dotmim.Sync.SqlServer;
using Microsoft.AspNetCore.Authentication.Certificate;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.HttpOverrides;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Primitives;
using NLog;
using signalr.Hubs;
using System;
using System.IO;
using System.Net;
using System.Net.Http;
using System.Security.Cryptography.X509Certificates;
using System.Threading.Tasks;
using System.Web.Http.Controllers;
using System.Web.Http.Filters;
namespace WebSyncServer {
public class Startup {
public static Microsoft.Extensions.Logging.ILogger _logger;
private Logger WebSyncProxyStartup = LogManager.GetLogger("WebSyncProxy");
public Startup(IConfiguration configuration) {
LogManager.LoadConfiguration(System.String.Concat(Directory.GetCurrentDirectory(), "/nlog.config"));
Configuration = configuration;
}
public IConfiguration Configuration {
get;
}
readonly string MyAllowSpecificOrigins = "_myAllowSpecificOrigins";
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services) {
// [Required]: Handling multiple sessions
services.AddMemoryCache();
services.AddAuthorization();
services.AddCors(options => {
options.AddPolicy(MyAllowSpecificOrigins,
builder => {
builder.WithOrigins("https://localhost:44369")
.AllowAnyHeader()
.WithMethods("GET", "POST")
.AllowCredentials();
});
});
//SignalR
services.AddSignalR().AddHubOptions < SyncHub > (options => {
options.EnableDetailedErrors = true;
});
services.AddControllers();
// Get a connection string to server data source
var connectionString = Configuration.GetSection("ConnectionStrings")["SqlConnection"];
var tables1 = new string[] { "" };
// ------------------------------------------------------------------------------------
// Add a SqlSyncProvider acting as the server hub for each possible scope name
// ------------------------------------------------------------------------------------
services.AddSyncServer < SqlSyncProvider > (connectionString, "Scope1", tables1);
services.AddSyncServer < SqlSyncProvider > (connectionString, "Scope2", tables1);
services.AddSyncServer < SqlSyncProvider > (connectionString, "Scope3", tables1);
services.AddSyncServer < SqlSyncProvider > (connectionString, "Scope4", tables1);
services.AddSyncServer < SqlSyncProvider > (connectionString, "Scope5", tables1);
}
public class RequireHttpsAttribute: AuthorizationFilterAttribute {
public override void OnAuthorization(HttpActionContext actionContext) {
if (actionContext.Request.RequestUri.Scheme != Uri.UriSchemeHttps) {
actionContext.Response = new HttpResponseMessage(System.Net.HttpStatusCode.Forbidden) {
ReasonPhrase = "HTTPS Required"
};
} else {
base.OnAuthorization(actionContext);
}
}
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env, ILogger < Startup > logger) {
if (env.IsDevelopment()) {
app.UseDeveloperExceptionPage();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseCertificateForwarding();
app.UseAuthentication();
app.UseCors(MyAllowSpecificOrigins);
app.UseAuthorization();
app.UseEndpoints(endpoints => {
endpoints.MapControllers();
endpoints.MapHub < SyncHub > ("/syncHub");
});
}
}
}
Thanks for sharing your code, it will help people, for sure !
I have a working solution that I need to add the ability to use multiple scopes. The local database server is SQL express and the remote database server is SQL standard all running on Windows with the sync proxy running on IIS. Using my client desktop application I can provision the remote database with multiple scopes. I have been able to successfully provision the server database with multiple scopes with each containing unique tables. The documentation shows a sample of how to sync a database to a client with multiple scopes using SyncAgent but it appears that the option to specify the scope name has been removed. For example "var setupScopeName = new SyncSetup(tables, currentScopeName);" the second parameter for SyncSetup is no longer a valid parameter. I can get the schema to download to the client but it's all in one scope "DefaultScope" and the sync_scope_setup column in the "scope_info" table on the client tends to truncate some of the data. On a side note some of the tables I'm dealing with have a space in the name (old legacy database). They will not provision with a space in the name even when bracketed like this [Action Links] in the table sync list. Removing the space works but breaks the old application using this table.
Server table "scope_info_server"
Client table "scope_info"