Trying to create the database using ABP framework while DB Migration - multi-tenant

I have inserted connectionstring in "[AbpTenantConnectionStrings]" table for same tenant id.
My objective is to create both the DB and migrage the DB
DB
while running below code only "Default Db is creating", can anyone let me know how to create and migrate other DB as well. what changes need to be done in the below code.
var tenants = await _tenantRepository.GetListAsync(includeDetails: true);
var migratedDatabaseSchemas = new HashSet<string>();
foreach (var tenant in tenants)
{
using (_currentTenant.Change(tenant.Id))
{
if (tenant.ConnectionStrings.Any())
{
var tenantConnectionStrings = tenant.ConnectionStrings
.Select(x => x.Value)
.ToList();
if (!migratedDatabaseSchemas.IsSupersetOf(tenantConnectionStrings))
{
await MigrateDatabaseSchemaAsync(tenant);
migratedDatabaseSchemas.AddIfNotContains(tenantConnectionStrings);
}
}
await SeedDataAsync(tenant);
}

After the below changes able to migrate or create the Db based on the connection-string inserted in "AbpTenantConnectionStrings":
public async Task MigrateAsync()
{
var initialMigrationAdded = AddInitialMigrationIfNotExist();
//var str = (await _connectionResolver.ResolveAsync("Default1"));
if (initialMigrationAdded)
{
return;
}
Logger.LogInformation("Started database migrations...");
await MigrateDatabaseSchemaAsync();
await SeedDataAsync();
Logger.LogInformation($"Successfully completed host database migrations.");
var tenants = await _tenantRepository.GetListAsync(includeDetails: true);
var migratedDatabaseSchemas = new HashSet<string>();
foreach (var tenant in tenants)
{
using (_currentTenant.Change(tenant.Id))
{
if (tenant.ConnectionStrings.Any())
{
var tenantConnectionStrings = tenant.ConnectionStrings
.Select(x => x.Value)
.ToList();
tenantConnectionStrings.ForEach(async s =>
{
await MigrateDatabaseSchemaAsync(s);
// migratedDatabaseSchemas.AddIfNotContains(s);
});
//if (!migratedDatabaseSchemas.IsSupersetOf(tenantConnectionStrings))
//{
// await MigrateDatabaseSchemaAsync(tenant);
// migratedDatabaseSchemas.AddIfNotContains(tenantConnectionStrings);
//}
}
await SeedDataAsync(tenant);
}
Logger.LogInformation($"Successfully completed {tenant.Name} tenant database migrations.");
}
Logger.LogInformation("Successfully completed all database migrations.");
Logger.LogInformation("You can safely end this process...");
}
private async Task MigrateDatabaseSchemaAsync(string str)
{
//Logger.LogInformation(
// $"Migrating schema for {(tenant == null ? "host" : tenant.Name + " tenant")} database...");
foreach (var migrator in _dbSchemaMigrators)
{
await migrator.MigrateAsync(str);
}
}
public interface IBookStoreDbSchemaMigrator
{
Task MigrateAsync();
Task MigrateAsync(string con);
}
public class EntityFrameworkCoreBookStoreDbSchemaMigrator
: IBookStoreDbSchemaMigrator, ITransientDependency
{
private readonly IServiceProvider _serviceProvider;
public EntityFrameworkCoreBookStoreDbSchemaMigrator(
IServiceProvider serviceProvider)
{
_serviceProvider = serviceProvider;
}
public async Task MigrateAsync()
{
/* We intentionally resolving the BookStoreMigrationsDbContext
* from IServiceProvider (instead of directly injecting it)
* to properly get the connection string of the current tenant in the
* current scope.
*/
await _serviceProvider.GetRequiredService<BookStoreMigrationsDbContext>()
.Database
.MigrateAsync();
}
public async Task MigrateAsync(string con)
{
var context = _serviceProvider
.GetRequiredService<BookStoreMigrationsDbContext>();
context.Database.SetConnectionString(con);
context.Database.Migrate();
}
}
Let me know if anyone has suggestions or a better approach.

Related

Log additional information with TranscriptLoggerMiddleware

I'm using TranscriptLoggerMiddleware to log transcript to Azure blobs.
Now, I want to add additional information to the activity, for example, account ID.
Ideally I want the account ID to be the top level folder when creating the blobs so one can easily locate all conversations for a given account.
The logger is only passed the activity without any context. So I'm looking at the Entities activity property which I can potentially use for storing my account ID.
Is this a valid approach?
Any other ideas on how to implement this?
Answering my own question.
This worked for me:
public class SetEntityMiddleware : IMiddleware
{
private readonly BotState _userState;
private readonly IStatePropertyAccessor<UserProfileState> _userProfileState;
public SetEntityMiddleware(UserState userState)
{
_userState = userState;
_userProfileState = userState.CreateProperty<UserProfileState>(nameof(UserProfileState));
}
public async Task OnTurnAsync(ITurnContext turnContext, NextDelegate next, CancellationToken cancellationToken = default)
{
var userProfile = await _userProfileState.GetAsync(turnContext, () => new UserProfileState(), cancellationToken);
this.SetEntity(turnContext.Activity, userProfile);
turnContext.OnSendActivities(async (ctx, activities, nextSend) =>
{
var userProfile = await _userProfileState.GetAsync(ctx, () => new UserProfileState(), cancellationToken);
foreach (var activity in activities)
{
this.SetEntity(activity, userProfile);
}
return await nextSend().ConfigureAwait(false);
});
await next(cancellationToken).ConfigureAwait(false);
}
private void SetEntity(Activity activity, UserProfileState userProfile)
{
if (activity.Type == ActivityTypes.Message &&
!string.IsNullOrEmpty(userProfile.AccountNumber))
{
var entity = new Entity();
entity.Type = "userProfile";
entity.Properties["accountNumber"] = userProfile.AccountNumber;
if (activity.Entities == null)
{
activity.Entities = new List<Entity>();
}
activity.Entities.Add(entity);
}
}
}

Mock IDocumentQuery with ability to use query expressions

I need to be able to mock IDocumentQuery, to be able to test piece of code, that queries document collection and might use predicate to filter them:
IQueryable<T> documentQuery = client
.CreateDocumentQuery<T>(collectionUri, options);
if (predicate != null)
{
documentQuery = documentQuery.Where(predicate);
}
var list = documentQuery.AsDocumentQuery();
var documents = new List<T>();
while (list.HasMoreResults)
{
documents.AddRange(await list.ExecuteNextAsync<T>());
}
I've used answer from https://stackoverflow.com/a/49911733/212121 to write following method:
public static IDocumentClient Create<T>(params T[] collectionDocuments)
{
var query = Substitute.For<IFakeDocumentQuery<T>>();
var provider = Substitute.For<IQueryProvider>();
provider
.CreateQuery<T>(Arg.Any<Expression>())
.Returns(x => query);
query.Provider.Returns(provider);
query.ElementType.Returns(collectionDocuments.AsQueryable().ElementType);
query.Expression.Returns(collectionDocuments.AsQueryable().Expression);
query.GetEnumerator().Returns(collectionDocuments.AsQueryable().GetEnumerator());
query.ExecuteNextAsync<T>().Returns(x => new FeedResponse<T>(collectionDocuments));
query.HasMoreResults.Returns(true, false);
var client = Substitute.For<IDocumentClient>();
client
.CreateDocumentQuery<T>(Arg.Any<Uri>(), Arg.Any<FeedOptions>())
.Returns(query);
return client;
}
Which works fine as long as there's no filtering using IQueryable.Where.
My question:
Is there any way to capture predicate, that was used to create documentQuery and apply that predicate on collectionDocuments parameter?
Access the expression from the query provider so that it will be passed on to the backing collection to apply the desired filter.
Review the following
public static IDocumentClient Create<T>(params T[] collectionDocuments) {
var query = Substitute.For<IFakeDocumentQuery<T>>();
var queryable = collectionDocuments.AsQueryable();
var provider = Substitute.For<IQueryProvider>();
provider.CreateQuery<T>(Arg.Any<Expression>())
.Returns(x => {
var expression = x.Arg<Expression>();
if (expression != null) {
queryable = queryable.Provider.CreateQuery<T>(expression);
}
return query;
});
query.Provider.Returns(_ => provider);
query.ElementType.Returns(_ => queryable.ElementType);
query.Expression.Returns(_ => queryable.Expression);
query.GetEnumerator().Returns(_ => queryable.GetEnumerator());
query.ExecuteNextAsync<T>().Returns(x => new FeedResponse<T>(query));
query.HasMoreResults.Returns(true, true, false);
var client = Substitute.For<IDocumentClient>();
client
.CreateDocumentQuery<T>(Arg.Any<Uri>(), Arg.Any<FeedOptions>())
.Returns(query);
return client;
}
The important part is where the expression passed to the query is used to create another query on the backing data source (the array).
Using the following example subject under test for demonstration purposes.
public class SubjectUnderTest {
private readonly IDocumentClient client;
public SubjectUnderTest(IDocumentClient client) {
this.client = client;
}
public async Task<List<T>> Query<T>(Expression<Func<T, bool>> predicate = null) {
FeedOptions options = null; //for dummy purposes only
Uri collectionUri = null; //for dummy purposes only
IQueryable<T> documentQuery = client.CreateDocumentQuery<T>(collectionUri, options);
if (predicate != null) {
documentQuery = documentQuery.Where(predicate);
}
var list = documentQuery.AsDocumentQuery();
var documents = new List<T>();
while (list.HasMoreResults) {
documents.AddRange(await list.ExecuteNextAsync<T>());
}
return documents;
}
}
The following sample tests when an expression is passed to the query
[TestMethod]
public async Task Should_Filter_DocumentQuery() {
//Arrange
var dataSource = Enumerable.Range(0, 3)
.Select(_ => new Document() { Key = _ }).ToArray();
var client = Create(dataSource);
var subject = new SubjectUnderTest(client);
Expression<Func<Document, bool>> predicate = _ => _.Key == 1;
var expected = dataSource.Where(predicate.Compile());
//Act
var actual = await subject.Query<Document>(predicate);
//Assert
actual.Should().BeEquivalentTo(expected);
}
public class Document {
public int Key { get; set; }
}

TaskContinuation.cs not found exception while accessing WebAPI Task

I'm trying to fetch records from a db cursor from the Client app.
Debugging Web API shows that the Cursor returns records but when returning to the Client it throws mscorlib.pdb not loaded window and clicking on Load option it throws TaskContinuation.cs not found exception
Code snippets as below ( removed irrelevant codes for readability )
WebAPI
[HttpPost("{values}")]
public async Task<ActionResult> Post([FromBody] JToken values)
{
// code removed for readility
string[] cursors = { };
cursors = await cursor.GetRpts();
CursorClass firstCursor = JsonConvert.DeserializeObject<CursorClass>(cursors[0]);
return new OkObjectResult(cursors);
}
public async Task<string[]> GetRpts()
{
try
{
DataTable[] dataTables;
CursorClass[] cursorClasses = new CursorClass[5];
//stripped some code
using (DataAccess dataAccess = new DataAccess()
{
ParamData = PrepareDoc(),
ProcedureName = Constants.Rpt,
RecordSets = this.CursorNumbers,
})
{
Int32 errorNumber = await dataAccess.RunComAsync();
dataTables = dataAccess.TableData;
};
//fetching code stripped off
string[] _cursors = Array.ConvertAll(cursorClasses, JsonConvert.SerializeObject);
return _cursors;
}
catch (Exception ex)
{
string tt = ex.Message;
}
}
public async Task<Int32> RunComAsync()
{
Int32 returnValue = 0;
try
{
//open db connection
//---------- Running the Command in asysnc mode ----------
Task<int> task = new Task<int>(oracleCommand.ExecuteNonQuery);
task.Start();
returnValue = await task;
//--------------------------------------------------------
OracleRefCursor[] refCursor = { null, null, null, null, null };
for (int _sub = 0; _sub < RecordSets; _sub++)
{
//DT declaration / connection code removed
dataAdapter.Fill(dataTable, refCursor[_sub]);
TableData[_sub] = dataTable;
}
}
catch (Exception ex)
{
return LogMsg(ex);
}
finally
{
this.Dispose(true);
}
CloseConnection();
return LogMsg(null,"Successful Operation");
}
Client
private async Task<HttpStatusCode> CallService()
{
HttpResponseMessage _response = null;
try
{
using (HttpRequestMessage requestMessage = new HttpRequestMessage()
{
Content = new System.Net.Http.StringContent(JsonRepo, System.Text.Encoding.UTF8, HeaderJson),
RequestUri = new Uri(UriString),
Method = HttpMethod.Post,
})
{
requestMessage.Headers.Authorization = new AuthenticationHeaderValue("Bearer", Utils.TOKEN) ;
_response = await httpClient.SendAsync(requestMessage);
if (_response.IsSuccessStatusCode)
{
string httpResponse = await _response.Content.ReadAsStringAsync();
httpString = JsonConvert.DeserializeObject<string[]>(httpResponse);
}
}
}
return ErrorCode;
}
Is that something related to async operation? while debugging the API it confirms the Datatable with records . Any inputs are deeply appreciated.
error image
TIA

Linq update Trouble

I want to update my Azure database using linq in my xamarin.forms project
below is the initalise and sync methods that connect to the database. These work successfully. My problem is that I want to update a record is the database. Any suggestions are welcome.
MobileServiceClient client = null;
IMobileServiceSyncTable<Shop> ShopTable2;
bool isInitialised;
public async Task Initialize()
{
if (isInitialised)
{
return;
}
this.client = new MobileServiceClient("link to database");
MobileServiceClient client = new MobileServiceClient("link to databse");
const string path = "user.db";
var store = new MobileServiceSQLiteStore(path);
store.DefineTable<Shop>();
await this.client.SyncContext.InitializeAsync(store, new MobileServiceSyncHandler());
ShopTable2= this.client.GetSyncTable<Shop>();
isInitialised = true;
}
sync method
public async Task Sync()
{
try
{
await shopTable2.PullAsync("allusers3", shopTable2.CreateQuery());
await client.SyncContext.PushAsync();
}
catch (Exception ex)
{
Debug.WriteLine("Unable to sync " + ex);
}
}
where I want to update the records here:
public async void BuyProducts(string Pname)
{
string productname = Pname;
await Initialize();
await SyncBookings();
List<Shop_TBL> item = await BookingsTable2
.Where(todoItem => todoItem.ProductName == productname)
.ToListAsync();
}
Assuming you are using Entity Framework as your ORM:
Select the entity you want to update.
var book = db.Books.First(b => b.Id == _id);
Change the property:
book.Name = "Something else";
Save the changes:
await db.SaveChangesAsync();
or
db.SaveChanges();
That's how you do an update.

Multiple owin listeners with their own set of controllers, with Autofac for DI

I am trying to use multiple in-process owin listeners. Each should have a distinct set of controllers, where they may have the same route handled by a different controller. For instance
localhost:1234/api/app/test should resolve to ControllerA
localhost:5678/api/app/test should resolve to ControllerB
controller a, in owin host 1, has route attribute
[Route("api/app/test")]
controller b, in owin host 2, has route attribute
[Route("api/app/{*path}")]
and is used to forward requests to the other owin host.
We are using Autofac for dependency injection. Routes are configured through attribute routing.
autofac requires a line such as
builder.RegisterApiControllers(typeof(ControllerA).Assembly)
Our OWIN configuration contains:
var config = ConfigureWebApi();
// Configure Autofac
config.DependencyResolver = new AutofacWebApiDependencyResolver(container);
app.UseAutofacMiddleware(container);
app.UseAutofacWebApi(config);
app.UseWebApi(config);
However when starting two listeners, I need to include both assemblies for controller resolving. This leads to a 'duplicate route' exception:
Multiple controller types were found that match the URL. This can
happen if attribute routes on multiple controllers match the requested
URL.\r\n\r\nThe request has found the following matching controller
types:
\r\nLib1.Controllers.ControllerA\r\nLib2.Controllers.ControllerB"
When running the OWIN listeners in separate processes, there are no issues.
I have also tried to use multiple DI containers, one for each OWIN listener, but that conflicts with Web Api 2 as it requires GlobalConfiguration.Configuration.DependencyResolver to be set. Which conflicts with the concept of multiple DI containers.
Can someone guide me how to configure such a setup?
Use the OWIN environment and customize the HttpControllerSelector
Using the OWIN pipeline you can pass information about the request to a custom HttpControllerSelector. This allows you to be selective about which controllers are used to match which routes.
Of course this is easier said than done. The inner workings of WebAPI with respect to routing are not very transparent - source code is often the best documentation in this area.
I could not get the HttpControllerSelector to fully work, so there's an ugly workaround in CustomHttpActionSelector. It may still be sufficient if all you need to do is forward requests from one host to the other.
The end result is:
GET to http://localhost:1234/api/app/test returns "HellofromAController" (directly invokes AController)
GET to http://localhost:5678/api/app/test returns "(FromBController): \"HellofromAController\"" (invokes BController, which forwards the request to AController)
See the full source on github
I left the logging code as-is in case it's useful, but it's not relevant to the solution.
So without further ado:
CustomHttpControllerSelector.cs:
Uses the port-specific OWIN env variable ApiControllersAssembly in to filter the controllers.
public sealed class CustomHttpControllerSelector : DefaultHttpControllerSelector
{
private static readonly ILog Logger;
static CustomHttpControllerSelector()
{
Logger = LogProvider.GetCurrentClassLogger();
}
public CustomHttpControllerSelector(HttpConfiguration configuration) : base(configuration)
{
}
public override HttpControllerDescriptor SelectController(HttpRequestMessage request)
{
var apiControllerAssembly = request.GetOwinEnvironment()["ApiControllersAssembly"].ToString();
Logger.Debug($"{nameof(CustomHttpControllerSelector)}: {{{nameof(apiControllerAssembly)}: {apiControllerAssembly}}}");
var routeData = request.GetRouteData();
var routeCollectionRoute = routeData.Route as IReadOnlyCollection<IHttpRoute>;
var newRoutes = new List<IHttpRoute>();
var newRouteCollectionRoute = new RouteCollectionRoute();
foreach (var route in routeCollectionRoute)
{
var filteredDataTokens = FilterDataTokens(route, apiControllerAssembly);
if (filteredDataTokens.Count == 2)
{
var newRoute = new HttpRoute(route.RouteTemplate, (HttpRouteValueDictionary)route.Defaults, (HttpRouteValueDictionary)route.Constraints, filteredDataTokens);
newRoutes.Add(newRoute);
}
}
var newRouteDataValues = new HttpRouteValueDictionary();
foreach (var routeDataKvp in routeData.Values)
{
var newRouteDataCollection = new List<IHttpRouteData>();
var routeDataCollection = routeDataKvp.Value as IEnumerable<IHttpRouteData>;
if (routeDataCollection != null)
{
foreach (var innerRouteData in routeDataCollection)
{
var filteredDataTokens = FilterDataTokens(innerRouteData.Route, apiControllerAssembly);
if (filteredDataTokens.Count == 2)
{
var newInnerRoute = new HttpRoute(innerRouteData.Route.RouteTemplate, (HttpRouteValueDictionary)innerRouteData.Route.Defaults, (HttpRouteValueDictionary)innerRouteData.Route.Constraints, filteredDataTokens);
var newInnerRouteData = new HttpRouteData(newInnerRoute, (HttpRouteValueDictionary)innerRouteData.Values);
newRouteDataCollection.Add(newInnerRouteData);
}
}
newRouteDataValues.Add(routeDataKvp.Key, newRouteDataCollection);
}
else
{
newRouteDataValues.Add(routeDataKvp.Key, routeDataKvp.Value);
}
HttpRouteData newRouteData;
if (newRoutes.Count > 1)
{
newRouteCollectionRoute.EnsureInitialized(() => newRoutes);
newRouteData = new HttpRouteData(newRouteCollectionRoute, newRouteDataValues);
}
else
{
newRouteData = new HttpRouteData(newRoutes[0], newRouteDataValues);
}
request.SetRouteData(newRouteData);
}
var controllerDescriptor = base.SelectController(request);
return controllerDescriptor;
}
private static HttpRouteValueDictionary FilterDataTokens(IHttpRoute route, string apiControllerAssembly)
{
var newDataTokens = new HttpRouteValueDictionary();
foreach (var dataToken in route.DataTokens)
{
var actionDescriptors = dataToken.Value as IEnumerable<HttpActionDescriptor>;
if (actionDescriptors != null)
{
var newActionDescriptors = new List<HttpActionDescriptor>();
foreach (var actionDescriptor in actionDescriptors)
{
if (actionDescriptor.ControllerDescriptor.ControllerType.Assembly.FullName == apiControllerAssembly)
{
newActionDescriptors.Add(actionDescriptor);
}
}
if (newActionDescriptors.Count > 0)
{
newDataTokens.Add(dataToken.Key, newActionDescriptors.ToArray());
}
}
else
{
newDataTokens.Add(dataToken.Key, dataToken.Value);
}
}
return newDataTokens;
}
}
CustomHttpActionSelector.cs:
You shouldn't need a CustomHttpActionSelector, this only exists to work around an issue with the ActionDescriptors for BController. It works as long as BController has only one method, otherwise you'll need to implement some route-specific logic.
public sealed class CustomHttpActionSelector : ApiControllerActionSelector
{
private static readonly ILog Logger;
static CustomHttpActionSelector()
{
Logger = LogProvider.GetCurrentClassLogger();
}
public override HttpActionDescriptor SelectAction(HttpControllerContext controllerContext)
{
try
{
var actionDescriptor = base.SelectAction(controllerContext);
return actionDescriptor;
}
catch (Exception ex)
{
Logger.WarnException(ex.Message, ex);
IDictionary<string, object> dataTokens;
var route = controllerContext.Request.GetRouteData().Route;
var routeCollectionRoute = route as IReadOnlyCollection<IHttpRoute>;
if (routeCollectionRoute != null)
{
dataTokens = routeCollectionRoute
.Select(r => r.DataTokens)
.SelectMany(dt => dt)
.ToDictionary(kvp => kvp.Key, kvp => kvp.Value);
}
else
{
dataTokens = route.DataTokens;
}
var actionDescriptors = dataTokens
.Select(dt => dt.Value)
.Where(dt => dt is IEnumerable<HttpActionDescriptor>)
.Cast<IEnumerable<HttpActionDescriptor>>()
.SelectMany(r => r)
.ToList();
return actionDescriptors.FirstOrDefault();
}
}
}
Program.cs:
internal class Program
{
private static readonly ILog Logger;
static Program()
{
Log.Logger = new LoggerConfiguration()
.WriteTo
.LiterateConsole()
.MinimumLevel.Is(LogEventLevel.Verbose)
.CreateLogger();
Logger = LogProvider.GetCurrentClassLogger();
}
internal static void Main(string[] args)
{
var builder = new ContainerBuilder();
builder.RegisterModule(new LogRequestModule());
builder.RegisterApiControllers(typeof(AController).Assembly);
builder.RegisterApiControllers(typeof(BController).Assembly);
var container = builder.Build();
var config = GetHttpConfig();
config.DependencyResolver = new AutofacWebApiDependencyResolver(container);
var options = new StartOptions();
options.Urls.Add("http://localhost:1234");
options.Urls.Add("http://localhost:5678");
var listener = WebApp.Start(options, app =>
{
app.Use((ctx, next) =>
{
if (ctx.Request.LocalPort.HasValue)
{
var port = ctx.Request.LocalPort.Value;
string apiControllersAssemblyName = null;
if (port == 1234)
{
apiControllersAssemblyName = typeof(AController).Assembly.FullName;
}
else if (port == 5678)
{
apiControllersAssemblyName = typeof(BController).Assembly.FullName;
}
ctx.Set("ApiControllersAssembly", apiControllersAssemblyName);
Logger.Info($"{nameof(WebApp)}: Port = {port}, ApiControllersAssembly = {apiControllersAssemblyName}");
}
return next();
});
app.UseAutofacMiddleware(container);
app.UseAutofacWebApi(config);
app.UseWebApi(config);
});
Logger.Info(#"Press [Enter] to exit");
Console.ReadLine();
listener.Dispose(); ;
}
private static HttpConfiguration GetHttpConfig()
{
var config = new HttpConfiguration();
config.MapHttpAttributeRoutes();
config.IncludeErrorDetailPolicy = IncludeErrorDetailPolicy.Always;
config.Services.Add(typeof(IExceptionLogger), new LogProviderExceptionLogger());
config.Formatters.Remove(config.Formatters.XmlFormatter);
config.Services.Replace(typeof(IHttpControllerSelector), new CustomHttpControllerSelector(config));
config.Services.Replace(typeof(IHttpActionSelector), new CustomHttpActionSelector());
var traceSource = new TraceSource("LibLog") { Switch = { Level = SourceLevels.All } };
traceSource.Listeners.Add(new LibLogTraceListener());
var diag = config.EnableSystemDiagnosticsTracing();
diag.IsVerbose = false;
diag.TraceSource = traceSource;
return config;
}
}
LibA\Controllers\AController.cs:
[RoutePrefix("api/app")]
public class AController : ApiController
{
private static readonly ILog Logger;
static AController()
{
Logger = LogProvider.GetCurrentClassLogger();
Logger.Debug($"{nameof(AController)}: Static Constructor");
}
public AController()
{
Logger.Debug($"{nameof(AController)}: Constructor");
}
[HttpGet, Route("test")]
public async Task<IHttpActionResult> Get()
{
Logger.Debug($"{nameof(AController)}: Get()");
return Ok($"Hello from {nameof(AController)}");
}
}
LibB\Controllers\BController.cs:
[RoutePrefix("api/app")]
public class BController : ApiController
{
private static readonly ILog Logger;
static BController()
{
Logger = LogProvider.GetCurrentClassLogger();
Logger.Debug($"{nameof(BController)}: Static Constructor");
}
public BController()
{
Logger.Debug($"{nameof(BController)}: Constructor");
}
[HttpGet, Route("{*path}")]
public async Task<IHttpActionResult> Get([FromUri] string path)
{
if (path == null)
{
path = Request.RequestUri.PathAndQuery.Split(new[] {"api/app/"}, StringSplitOptions.RemoveEmptyEntries)[1];
}
Logger.Debug($"{nameof(BController)}: Get({path})");
using (var client = new HttpClient {BaseAddress = new Uri("http://localhost:1234/api/app/")})
{
var result = await client.GetAsync(path);
var content = await result.Content.ReadAsStringAsync();
return Ok($"(From {nameof(BController)}): {content}");
}
}
}
I might have another go at it when I have more time.
Let me know if you make any progress!

Resources