Disable Wrapping of Controller Results - aspnetboilerplate

I am currently using v3.2.5 of Abp.AspNetCore.
I am trying to integrate an Alpha package of Microsoft.AspNetCore.OData into the project which is so far looking ok.
However when i try and query the metadata controller http://localhost:51078/odata/v1/$metadata the result is wrapped.
Now this was an issue for the ODataControllers as well, but i could simply add
the [DontWrapResult] attribute.
I dont have direct access to the MetadataController so i am unable to add the attribute. Is there anyway to disable wrapping for an Abp project?
Thanks
Edit
Here is the current ConfigureServices method
public IServiceProvider ConfigureServices(IServiceCollection services)
{
services
.AddMvc()
.AddJsonOptions(options => { options.SerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore; });
services
.AddAuthentication()
.AddCsDeviceAuth(options => { });
services
.AddOData();
//Configure Abp and Dependency Injection
var provider = services.AddAbp<PortalWebODataModule>(options =>
{
//Configure Log4Net logging
options.IocManager.IocContainer.AddFacility<LoggingFacility>(
f => f.LogUsing<Log4NetLoggerFactory>().WithConfig("log4net.config")
);
});
services.Configure<MvcOptions>(options =>
{
var abpResultFilter = options.Filters.First(f => f is AbpResultFilter);
options.Filters.Remove(abpResultFilter);
options.Filters.AddService(typeof(ODataResultFilter));
});
return provider;
}

You can implement IResultFilter and set WrapOnSuccess to false:
public class ResultFilter : IResultFilter, ITransientDependency
{
private readonly IAbpAspNetCoreConfiguration _configuration;
public ResultFilter(IAbpAspNetCoreConfiguration configuration)
{
_configuration = configuration;
}
public void OnResultExecuting(ResultExecutingContext context)
{
if (context.HttpContext.Request.Path.Value.Contains("odata"))
{
var methodInfo = context.ActionDescriptor.GetMethodInfo();
var wrapResultAttribute =
GetSingleAttributeOfMemberOrDeclaringTypeOrDefault(
methodInfo,
_configuration.DefaultWrapResultAttribute
);
wrapResultAttribute.WrapOnSuccess = false;
}
}
public void OnResultExecuted(ResultExecutedContext context)
{
// No action
}
private TAttribute GetSingleAttributeOfMemberOrDeclaringTypeOrDefault<TAttribute>(MemberInfo memberInfo, TAttribute defaultValue = default(TAttribute), bool inherit = true)
where TAttribute : class
{
return memberInfo.GetCustomAttributes(true).OfType<TAttribute>().FirstOrDefault()
?? memberInfo.DeclaringType?.GetTypeInfo().GetCustomAttributes(true).OfType<TAttribute>().FirstOrDefault()
?? defaultValue;
}
}
Then, in Startup class, add the filter in ConfigureServices method:
services.AddMvc(options =>
{
options.Filters.AddService(typeof(ResultFilter));
});
References:
AbpResultFilter.OnResultExecuting
ReflectionHelper.GetSingleAttributeOfMemberOrDeclaringTypeOrDefault

Alternative solution; to completely disable WrapResult behavior within the system ( at the Core module registration):
var abpAspNetCoreConfiguration = Configuration.Modules.AbpAspNetCore();
abpAspNetCoreConfiguration.DefaultWrapResultAttribute.WrapOnSuccess = false;
abpAspNetCoreConfiguration.DefaultWrapResultAttribute.WrapOnError = false;
abpAspNetCoreConfiguration
.CreateControllersForAppServices(
typeof(AccessApplicationModule).GetAssembly()
);
WrapOnSuccess and WrapOnError flags can be set to false values.

ABP v6.5 and later
Implement IWrapResultFilter and add it to WrapResultFilters in the module's PreInitialize method.
See https://stackoverflow.com/questions/70947461/how-to-control-response-wrapping-in-abp-on-a-per-route-basis/70955045#70955045 for more details.
Before ABP v6.5
...including ABP v3.2.5 mentioned in the question.
Subclass AbpResultFilter:
using Abp.AspNetCore.Configuration;
using Abp.AspNetCore.Mvc.Results;
using Abp.AspNetCore.Mvc.Results.Wrapping;
using Microsoft.AspNetCore.Mvc.Filters;
using System;
namespace AbpODataDemo.Web.Host.Filters
{
public class ODataResultFilter : AbpResultFilter
{
public ODataResultFilter(IAbpAspNetCoreConfiguration configuration, IAbpActionResultWrapperFactory actionResultWrapperFactory)
: base(configuration, actionResultWrapperFactory)
{
}
public override void OnResultExecuting(ResultExecutingContext context)
{
if (context.HttpContext.Request.Path.Value.StartsWith("/odata", StringComparison.InvariantCultureIgnoreCase))
{
return;
}
base.OnResultExecuting(context);
}
}
}
Replace AbpResultFilter with it in the Startup ConfigureServices method:
services.PostConfigure<MvcOptions>(options =>
{
var index = options.Filters.IndexOf(new ServiceFilterAttribute(typeof(AbpResultFilter)));
if (index != -1)
{
options.Filters.RemoveAt(index);
options.Filters.Insert(index, new ServiceFilterAttribute(typeof(ODataResultFilter)));
}
});
Reference: https://github.com/aspnetboilerplate/sample-odata/pull/16

Related

DelegateEndpointDefinition IsTemporary

I have a simple ConsumerDefinition:
public class HealthCheckConsumerDefinition : ConsumerDefinition<LoopbackConsumer>
{
public HealthCheckConsumerDefinition(IOptions<HealthCheckOptions> options)
{
EndpointName = options.Value.HostName;
Endpoint(configurator => configurator.Temporary = true); // not work
}
protected override void ConfigureConsumer(IReceiveEndpointConfigurator endpointConfigurator, IConsumerConfigurator<LoopbackConsumer> consumerConfigurator)
{
endpointConfigurator.ConfigureConsumeTopology = false;
base.ConfigureConsumer(endpointConfigurator, consumerConfigurator);
}
}
I expect the Temporary=true property to configure an endpoint with the AutoDelete = true and Durable = false properties, because this code is in the ApplyEndpointDefinition method of the RabbitMqHostConfiguration class:
https://github.com/MassTransit/MassTransit/blob/5fb6b4e31582970b0571e9fe6ac77793a0b3242a/src/MassTransit.RabbitMqTransport/Configuration/Configuration/RabbitMqHostConfiguration.cs#L60
public void ApplyEndpointDefinition(IRabbitMqReceiveEndpointConfigurator configurator, IEndpointDefinition definition)
{
if (definition.IsTemporary)
{
configurator.AutoDelete = true;
configurator.Durable = false;
}
...
}
But in the ConfigureEndpoints method of the class, the ConsumerDefinition registry is converted to the DelegateEndpointDefinition, which does not override the IsTemporary property based on the ConsumerDefinition object passed to the constructor:
https://github.com/MassTransit/MassTransit/blob/89ba77036230a15be108e8ade3a0e6fe5309a94d/src/MassTransit/Configuration/Registration/Registration.cs#L178
How to get around this problem and declare Temporary endpoint within the ConsumerDefinition?
I have confirmed that this works as expected in the upcoming MassTransit v7 release. Using this syntax, the temporary flag is now passed through to the transport.
Note that this would be overridden by using .Endpoint() following the AddConsumer<T>() container configuration method.
class RequestConsumerDefinition :
ConsumerDefinition<RequestConsumer>
{
public RequestConsumerDefinition()
{
Endpoint(e => e.Temporary = true);
}
}

Change JSON serialization from camelCase to PascalCase [Duplicate-No solution?]

I know this is duplicate of some past questions.But there were no real solution.
One of the related links
Another one
I am working with .Net Core 3 Multipage Template.
I've tried everything like given below. But no use.
This is my interface from application project:
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Abp.Application.Services;
using Abp.Application.Services.Dto;
using TSE.Kalibrasyon.Roles.Dto;
using TSE.Kalibrasyon.Users.Dto;
namespace TSE.Kalibrasyon.Labs
{
public interface ILabAppService : IApplicationService
{
string Test();
Task<List<Entities.Labs.Labs>> GetAllAsync();
System.Threading.Tasks.Task Update(Entities.Labs.Labs input);
System.Threading.Tasks.Task Create(Entities.Labs.Labs input);
}
}
and the implementation is:
using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using Abp.Application.Services;
using Abp.Application.Services.Dto;
using Abp.Authorization;
using Abp.Domain.Entities;
using Abp.Domain.Repositories;
using Abp.Extensions;
using Abp.IdentityFramework;
using Abp.Linq.Extensions;
using Abp.Localization;
using Abp.Runtime.Session;
using Abp.UI;
using TSE.Kalibrasyon.Authorization;
using TSE.Kalibrasyon.Authorization.Accounts;
using TSE.Kalibrasyon.Authorization.Roles;
using TSE.Kalibrasyon.Authorization.Users;
using TSE.Kalibrasyon.Roles.Dto;
using TSE.Kalibrasyon.Users.Dto;
using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;
using TSE.Kalibrasyon.Entities.Labs.Dto;
using TSE.Kalibrasyon.Entities.Labs;
using TSE.Kalibrasyon.Labs.Dto;
using Abp.Web.Models;
using Newtonsoft.Json;
using Microsoft.AspNetCore.Mvc;
namespace TSE.Kalibrasyon.Labs
{
//[AbpAuthorize(PermissionNames.Pages_Users)]
public class LabAppService : KalibrasyonAppServiceBase, ILabAppService
{
private readonly IRepository<Entities.Labs.Labs> _labRepository;
public LabAppService(IRepository<Entities.Labs.Labs> labRepository)
{
_labRepository = labRepository;
}
[Microsoft.AspNetCore.Mvc.HttpGet]
public string Test()
{
return "merhaba";
}
[DontWrapResult]
public async Task<List<Entities.Labs.Labs>> GetAllAsync()
{
//var chk = await _labRepository.GetAllListAsync();
return await _labRepository.GetAllListAsync();
}
[DontWrapResult]
//public List<Entities.Labs.Labs> GetAll2()
public object GetAll2()
{
List<Entities.Labs.Labs> chk = _labRepository.GetAllListAsync().Result;
//return _labRepository.GetAllListAsync().Result;
var bak= new { Items = chk, Count = chk.Count() };
return new { Items = chk, Count = chk.Count() };
//return Json(new { Items = chk, Count = chk.Count() }, new JsonSerializerSettings { ContractResolver = new PascalCasePropertyNamesContractResolver() });
}
[DontWrapResult]
public string GetAll3()
{
List<Entities.Labs.Labs> chk = _labRepository.GetAllListAsync().Result;
var obj= new { Items = chk, Count = chk.Count() };
//return Json(new { Items = chk, Count = chk.Count() }, new JsonSerializerSettings { ContractResolver = new PascalCasePropertyNamesContractResolver() });
var settings = new JsonSerializerSettings { TypeNameHandling = TypeNameHandling.All };
//var text = JsonConvert.SerializeObject(configuration, settings);
var text = JsonConvert.SerializeObject(obj);
return text;
//return Json(new { Items = chk, Count = chk.Count() });
}
public async Task Update(Entities.Labs.Labs input)
{
await _labRepository.UpdateAsync(input);
}
public async Task Create(Entities.Labs.Labs input)
{
await _labRepository.InsertAsync(input);
}
}
//public class Data
//{
// public bool requiresCounts { get; set; }
// public int skip { get; set; }
// public int take { get; set; }
//}
}
and the response body is:
[
{
"labName": "BASINÇ KALİBRASYON LABORATUVARI",
"labKod": "BAS",
"bolgeKodu": 5,
"id": 1
}
]
My model for this entity is :
using Abp.Domain.Entities;
using Abp.Domain.Repositories;
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations.Schema;
using System.Text;
namespace TSE.Kalibrasyon.Entities.Labs
{
[Table("Labs")]
public partial class Labs : Entity
{
private readonly IRepository<Labs> _lablarRepository;
//private readonly TownAppService _townAppService;
//private readonly IRepository<Town> _townRepository;
public Labs()
{
//this.Towns = new List<Town>();
//this.Districts = new List<District>();
//this.Neighborhoods = new List<Neighborhood>();
OnCreated();
}
//[System.ComponentModel.DataAnnotations.Key]
//[System.ComponentModel.DataAnnotations.Required()]
//public virtual int Id
//{
// get;
// set;
//}
[System.ComponentModel.DataAnnotations.Required()]
public virtual string LabName
{
get;
set;
}
[System.ComponentModel.DataAnnotations.StringLength(3)]
[System.ComponentModel.DataAnnotations.Required()]
public virtual string LabKod
{
get;
set;
}
[System.ComponentModel.DataAnnotations.Required()]
public virtual int BolgeKodu
{
get;
set;
}
#region Extensibility Method Definitions
partial void OnCreated();
#endregion
}
}
And there is an another big problem also, I am using Syncfusion .Net Core Grid as a third party tool. It requires a method for database operations as given below.
[IgnoreAntiforgeryToken]
public IActionResult UrlDatasource([FromBody]DataManagerRequest dm)
{
Api api=new Api();
//IEnumerable DataSource = Orders.GetAllRecords();
IEnumerable DataSource = api.LabsGetAll();
DataOperations operation = new DataOperations();
if (dm.Search != null && dm.Search.Count > 0)
{
DataSource = operation.PerformSearching(DataSource, dm.Search); //Search
}
if (dm.Sorted != null && dm.Sorted.Count > 0) //Sorting
{
DataSource = operation.PerformSorting(DataSource, dm.Sorted);
}
if (dm.Where != null && dm.Where.Count > 0) //Filtering
{
DataSource = operation.PerformFiltering(DataSource, dm.Where, dm.Where[0].Operator);
}
int count = DataSource.Cast<Entities.Labs.Labs>().Count();
if (dm.Skip != 0)
{
DataSource = operation.PerformSkip(DataSource, dm.Skip); //Paging
}
if (dm.Take != 0)
{
DataSource = operation.PerformTake(DataSource, dm.Take);
}
return dm.RequiresCounts ? Json(new { result = DataSource, count = count }) : Json(DataSource);
}
When I don't use the [IgnoreAntiforgeryToken] for the method. It doesn't hit the method with the HTTP ERROR 415.
Syncfusion says that, it is just because of camel casing and offers adding the
services.PostConfigure<MvcJsonOptions>(options =>
{
options.SerializerSettings.ContractResolver = new DefaultContractResolver();
});
into ConfigureServices method of StartUp file. But I think it works only with the .net core 2.x. It was not a solution for me with ABP working on .net core 3.0
My StartUp.cs in Host App is:
using System;
using System.Linq;
using System.Reflection;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Castle.Facilities.Logging;
using Abp.AspNetCore;
using Abp.AspNetCore.Mvc.Antiforgery;
using Abp.Castle.Logging.Log4Net;
using Abp.Extensions;
using TSE.Kalibrasyon.Configuration;
using TSE.Kalibrasyon.Identity;
using Abp.AspNetCore.SignalR.Hubs;
using Abp.Dependency;
using Abp.Json;
using Microsoft.OpenApi.Models;
using Newtonsoft.Json.Serialization;
using Microsoft.AspNetCore.Mvc;
namespace TSE.Kalibrasyon.Web.Host.Startup
{
public class Startup
{
private const string _defaultCorsPolicyName = "localhost";
private readonly IConfigurationRoot _appConfiguration;
public Startup(IWebHostEnvironment env)
{
_appConfiguration = env.GetAppConfiguration();
}
public IServiceProvider ConfigureServices(IServiceCollection services)
{
//MVC
services.AddControllersWithViews(
options =>
{
options.Filters.Add(new AbpAutoValidateAntiforgeryTokenAttribute());
}
).AddNewtonsoftJson(options =>
{
//options.SerializerSettings.ContractResolver = new AbpMvcContractResolver(IocManager.Instance)
//{
// NamingStrategy = new CamelCaseNamingStrategy()
//};
options.SerializerSettings.ContractResolver = new DefaultContractResolver();
});
services.PostConfigure<MvcNewtonsoftJsonOptions>(options =>
{
options.SerializerSettings.ContractResolver = new DefaultContractResolver();
});
IdentityRegistrar.Register(services);
AuthConfigurer.Configure(services, _appConfiguration);
services.AddSignalR();
// Configure CORS for angular2 UI
services.AddCors(
options => options.AddPolicy(
_defaultCorsPolicyName,
builder => builder
.WithOrigins(
// App:CorsOrigins in appsettings.json can contain more than one address separated by comma.
_appConfiguration["App:CorsOrigins"]
.Split(",", StringSplitOptions.RemoveEmptyEntries)
.Select(o => o.RemovePostFix("/"))
.ToArray()
)
.AllowAnyHeader()
.AllowAnyMethod()
.AllowCredentials()
)
);
// Swagger - Enable this line and the related lines in Configure method to enable swagger UI
services.AddSwaggerGen(options =>
{
options.SwaggerDoc("v1", new OpenApiInfo() { Title = "Kalibrasyon API", Version = "v1" });
options.DocInclusionPredicate((docName, description) => true);
// Define the BearerAuth scheme that's in use
options.AddSecurityDefinition("bearerAuth", new OpenApiSecurityScheme()
{
Description = "JWT Authorization header using the Bearer scheme. Example: \"Authorization: Bearer {token}\"",
Name = "Authorization",
In = ParameterLocation.Header,
Type = SecuritySchemeType.ApiKey
});
});
// Configure Abp and Dependency Injection
return services.AddAbp<KalibrasyonWebHostModule>(
// Configure Log4Net logging
options => options.IocManager.IocContainer.AddFacility<LoggingFacility>(
f => f.UseAbpLog4Net().WithConfig("log4net.config")
)
);
}
public void Configure(IApplicationBuilder app, ILoggerFactory loggerFactory)
{
app.UseAbp(options => { options.UseAbpRequestLocalization = false; }); // Initializes ABP framework.
app.UseCors(_defaultCorsPolicyName); // Enable CORS!
app.UseStaticFiles();
app.UseRouting();
app.UseAuthentication();
app.UseAbpRequestLocalization();
app.UseEndpoints(endpoints =>
{
endpoints.MapHub<AbpCommonHub>("/signalr");
endpoints.MapControllerRoute("default", "{controller=Home}/{action=Index}/{id?}");
endpoints.MapControllerRoute("defaultWithArea", "{area}/{controller=Home}/{action=Index}/{id?}");
});
// Enable middleware to serve generated Swagger as a JSON endpoint
app.UseSwagger();
// Enable middleware to serve swagger-ui assets (HTML, JS, CSS etc.)
app.UseSwaggerUI(options =>
{
options.SwaggerEndpoint(_appConfiguration["App:ServerRootAddress"].EnsureEndsWith('/') + "swagger/v1/swagger.json", "Kalibrasyon API V1");
options.IndexStream = () => Assembly.GetExecutingAssembly()
.GetManifestResourceStream("TSE.Kalibrasyon.Web.Host.wwwroot.swagger.ui.index.html");
}); // URL: /swagger
}
}
}
Is there any solution that you might know? Thanks in advance.
and also, Controlling serialization of property names with JsonResult. But no use.Any idea please?
In .NET Core 3.x, you now need to modify JsonSerializerOptions.PropertyNamingPolicy.
For PascalCase — following your original property name — set to null:
services.AddMvc(...)
.AddJsonOptions(jsonOptions =>
{
jsonOptions.JsonSerializerOptions.PropertyNamingPolicy = null;
});
For lowercase (or another custom naming policy), subclass JsonNamingPolicy and override the ConvertName method.
services.AddMvc(...)
.AddJsonOptions(jsonOptions =>
{
jsonOptions.JsonSerializerOptions.PropertyNamingPolicy = new JsonLowercaseNamingPolicy();
});
public class JsonLowercaseNamingPolicy : JsonNamingPolicy
{
public override string ConvertName(string name) => name.ToLowerInvariant();
}
Reference: https://learn.microsoft.com/en-us/dotnet/standard/serialization/system-text-json-how-to?view=netcore-3.1#use-a-custom-json-property-naming-policy
In .NET 6.0, I managed to fix the same issue, just add this code inside Program.cs (ofc, with Newtonsoft package previously installed.):
builder.Services.AddControllers().AddJsonOptions(options =>
options.JsonSerializerOptions.PropertyNamingPolicy = null);
For newtonsoft this is the implementation.
.AddControllersWithViews()
.AddNewtonsoftJson(options =>
{
options.SerializerSettings.ContractResolver = null;
}
);
Hope it helps.

ReactiveUI not responding to changes in test (with Autofixture)

Look at the following codes (simple ones)
public class NewEventViewModel : ReactiveObject
{
readonly ObservableAsPropertyHelper<bool> isSendVisible;
public bool IsSendVisible
{
get { return isSendVisible.Value; }
}
private ReactiveList<string> users;
public ReactiveList<string> Users
{
get { return this.users; }
set { this.RaiseAndSetIfChanged(ref users, value); }
}
public NewEventViewModel()
{
Users = new ReactiveList<string>(new List<string>())
{
ChangeTrackingEnabled = true
};
Users.CountChanged
.Select(x => x > 0)
.ToProperty(this, x => x.IsSendVisible, out isSendVisible);
//Users.Add("commented");
}
}
And the test:
[Fact]
public void ShouldBeVisibleWhenIsAtLeastOneUser()
{
//var sut = new NewEventViewModel();
var fixture = new Fixture();
var sut = fixture.Create<NewEventViewModel>();
sut.Users.Add("something");
sut.IsSendVisible.ShouldBeTrue();
}
The test fails... but when I uncomment the line from ViewModel, it passes.
Looks like the test is ignoring changes to Users. It works when I create the sut manually (new NewEventViewModel()). Why is AutoFixture breaking the test in such a strange way? What am I doing wrong?
You can use
fixture.Build<NewEventViewModel>().OmitAutoProperties().Create()
to temporarily turn off auto-properties.

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!

Can I call MVVM cross command method from Ios based on condition?

I have below MVVM cross command in my viewmodel. I want to call this based on condition from iOS. Is this possible?
Command
public IMvxCommand LoginCommand
{
get
{
return _loginCommand ?? (_loginCommand = new MvxCommand(async () => await ExecLoginClick()));
}
}
iOS Binding
var bindings = this.CreateBindingSet<LoginView, LoginViewModel>();
bindings.Bind(username).To(vm => vm.Email);
bindings.Bind(password).To(vm => vm.Password);
bindings.Bind(login_button).To(vm => vm.LoginCommand);
bindings.Bind(forgot_button).To(vm => vm.ForgotCommand);
bindings.Bind(register_button).To(vm => vm.GetSignUpCommand);
//bindings.Bind(btn_facebook).To(vm=>vm.)
bindings.Apply();
You can use CanExecute for this.
public IMvxCommand LoginCommand
{
get
{
return _loginCommand ??
(_loginCommand = new MvxAsyncCommand(ExecLoginClick, CanLogin));
}
}
private bool CanLogin()
{
if ( /*your condition*/)
{
return true;
}
return false;
}
private Task ExecLoginClick()
{
//...
}
And in every method, that affects your condition. You have to call
LoginCommand.RaiseCanExecuteChanged();
The Button is disabled or enabled based on the return value of CanExecute.
If you want to execute your command from your view, you should inherit from the generic MvxViewController<T> or MvxActivity<T> like.
public class LoginView : MvxViewController<LoginViewViewModel>
// or
public class LoginView : MvxActivity<LoginViewViewModel>
And then you can call
if(/*condition*/)
{
ViewModel.LoginCommand.Execute();
}

Resources