Dapr Azure Storage Queue input binding could not invoke OPTIONS method on input binding subscription endpoint - azure-storage-queues

I have followed the Dapr docs and am trying to set up an azure storage queue input binding for a dotnet 6 webapi project. The webapi project works as expected when run locally but when run through dapr fails to initialze and exist with the following error. Because Dapr issues an options method ond the binding endpoint I have includeded a options endpoint of my own. I have also added cors support (commented ot now). But nothing seems to help. Has anyone encountered this before. All help welcomed.
"could not invoke OPTIONS method on input binding subscription endpoint \"Process\": %!w(*errors.errorString=&{the server closed connection before returning the first response byte. Make sure the server returns 'Connection: close' response header before closing the connection})"
.\Component\binding.yaml (redacted connection details)
apiVersion: dapr.io/v1alpha1
kind: Component
metadata:
name: Process
namespace: default
spec:
type: bindings.azure.storagequeues
version: v1
metadata:
- name: accountName
value: "******"
- name: accountKey
value: "******"
- name: queueName
value: "queue-name"
- name: ttlInSeconds
value: "60"
- name: decodeBase64
value: "true"
.\program.cs
var builder = WebApplication.CreateBuilder(args);
//var MyAllowSpecificOrigins = "_myAllowSpecificOrigins";
//builder.Services.AddCors(options =>
//{
// options.AddPolicy(name: MyAllowSpecificOrigins,
// policy =>
// {
// policy.WithOrigins("http://localhost", "*");
// });
//});
builder.Services.AddControllers();
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
var app = builder.Build();
app.UseSwagger();
app.UseSwaggerUI();
app.UseHttpsRedirection();
//app.UseCors(MyAllowSpecificOrigins);
app.UseAuthorization();
app.MapControllers();
app.Run();
.\Controller\TestController.cs
using Microsoft.AspNetCore.Mvc;
[ApiController]
public class TestController : ControllerBase
{
private readonly ILogger<TestController> _logger;
public TestController(ILogger<TestController> logger)
{
_logger = logger;
}
[HttpPost("Process")]
public ActionResult<string> Process([FromBody] Message message)
{
try
{
var messageJson = System.Text.Json.JsonSerializer.Serialize(message);
_logger.LogInformation($"SUCCESS: MessageArrived: {messageJson}");
return Ok(message.Id);
}
catch (Exception ex)
{
_logger.LogError(ex, "ERROR: ");
return BadRequest(ex.Message);
}
}
[HttpOptions("Process")]
public ActionResult<string> ProcessOptions([FromBody] object message)
{
return Ok();
}
}
public class Message
{
public string Id { get; set; }
public string Name { get; set; }
}
Startup command
dapr run --log-level debug --app-id dnx --app-port 7221 --dapr-http-port 3500 dotnet run test.csproj

I had the same problem. The only thing I changed was my "applicationUrl" on launchSettings.json.
I just changed from 'https' to 'http' and it worked.

The problem was one of connectivity. The web app was not accessible in the exposed port. The options endpoints are required (webapi does not seem to add options endpoints to defined endpoints) and the UseHttpsRedirection method needed removing.

Related

How can I use an API controller as a service in an ASP.NET Core application?

I'm new to entity framework and have built a working API with a controller that I'll call APIController. My aim is to use the API as a 'service' in my ASP.NET Core MVC app which is in the same project but a different solution by having an instance of the API controller in my MVC controller and using it for the View requests.
The issue is that doing it this way means I have two contexts: one for the database in the API 'TTContext', which is the main one used to actually edit the files and another in the MVC 'TraineeTrackerContext'which is required for userManager authentication because it needs to inherent IdentityDbContext (which can't be done in the API).
I've tried to introduce these contexts in the program.cs as below:
using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;
using SpartaTraineeTrackerAPI.Models;
using SpartaTraineeTrackerAPI.Service;
using TraineeTrackerMVC.Data;
using SpartaTraineeTrackerAPI.Models;
using SpartaTraineeTrackerAPI.Service;
using TraineeTrackerMVC.Models;
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
var connectionString = builder.Configuration.GetConnectionString("DefaultConnection") ?? throw new InvalidOperationException("Connection string 'DefaultConnection' not found.");
builder.Services.AddDbContext<TTContext>(options =>
options.UseSqlServer(connectionString));
builder.Services.AddDbContext<TraineeTrackerContext>(options => options.UseSqlServer(connectionString));
builder.Services.AddDatabaseDeveloperPageExceptionFilter();
builder.Services.AddDefaultIdentity<User>(options => options.SignIn.RequireConfirmedAccount = true)
.AddEntityFrameworkStores<TTContext>();
builder.Services.AddControllersWithViews();
builder.Services.AddScoped<ITrackerService, TrackerService>();
var app = builder.Build();
// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
app.UseMigrationsEndPoint();
}
else
{
app.UseExceptionHandler("/Home/Error");
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseAuthorization();
app.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
app.MapRazorPages();
app.Run();
With the idea being that the TraineeTrackerContext has an instance of the TTContext attached like this:
using System;
using System.Collections.Generic;
using Microsoft.EntityFrameworkCore;
using TraineeTrackerMVC.Models;
using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
using SpartaTraineeTrackerAPI.Models;
namespace TraineeTrackerMVC.Data;
public partial class TraineeTrackerContext : IdentityDbContext
{
public TraineeTrackerContext()
{
ttContext = new TTContext();
}
public TraineeTrackerContext(TTContext context)
{
ttContext = context;
}
/*
public TraineeTrackerContext(DbContextOptions<TraineeTrackerContext> options)
: base(options)
{
ttContext = new TTContext(options);
}
*/
public TTContext ttContext;
public virtual DbSet<Profile> Profiles { get; set; }
public virtual DbSet<Tracker> Trackers { get; set; }
public virtual DbSet<User> Users { get; set; }
public TraineeTrackerContext(DbSet<Tracker> trackers, DbSet<User> users, DbSet<Profile> profiles)
{
Trackers = trackers;
Users = users;
Profiles = profiles;
}
}
But running it throws two exceptions when doing builder.Build():
InvalidOperationException: Error while validating the service descriptor ServiceType
System.AggregateException: Unable to activate type Trainee TrackerContext
I've been trying to fix these myself but I'm very new to Entity Framework and this is one of the first applications I've made so I was hoping someone could help explain where I'm going wrong and perhaps how to do this correctly. I haven't found many resources online using the API as a service in this way, as others tend to use the HttpClient class. Any help would be appreciated.

How to access appsettings.json from module Configure Services

I have a blazor server side solution that contains an appsettings.json
I am configuring the blob storage in the ConfigureServices override in the Application Module of the Applications project. It currently has a hard coded connection string and is working perfectly.
Now I want to move the connection string to the appsettings.json file that is in the Blazor project.
I've tried to inject the IConfiguration into the constructor of the ApplicationModule, but the app throws an error when I try to do so.
I've searched through the ServiceConfigurationContext passed into to the ConfigureServices override. There is a Service property containing a collection of around 1,024 ServiceDescriptors and found one that contains the word IConfiguration in the ServiceType.FullName but haven't been able to figure out how to use it to get at the service itself in order to get at the appsettings.json values.
Can anyone shed any light on how to access the appsettings.json values from the application module?
Here is my code I am working with
namespace MyApp
{
[DependsOn(
typeof(MyAppDomainModule),
typeof(AbpAccountApplicationModule),
typeof(MyAppApplicationContractsModule),
typeof(AbpIdentityApplicationModule),
typeof(AbpPermissionManagementApplicationModule),
typeof(AbpTenantManagementApplicationModule),
typeof(AbpFeatureManagementApplicationModule),
typeof(AbpSettingManagementApplicationModule),
typeof(AbpBlobStoringModule),
typeof(AbpBlobStoringAzureModule)
)]
public class MyAppApplicationModule : AbpModule
{
public override void ConfigureServices(ServiceConfigurationContext context)
{
Configure<AbpBlobStoringOptions>(options =>
{
options.Containers.ConfigureDefault(container =>
{
container.UseAzure(azure =>
{
azure.ConnectionString = "DefaultEndpointsProtocol=https;AccountName=MyApplocalsa;AccountKey=<truncated-account-key>;EndpointSuffix=core.windows.net";
azure.ContainerName = "Pictures";
azure.CreateContainerIfNotExists = true;
});
});
});
}
}
}
This answer has been update based on new information in the question.
If I understand the context correctly you are building your own DI services container within MyAppApplicationModule. As I don't have enough detail on MyAppApplicationModule, I'll demonstrate how you get to apllication configuration data in the context of OwningComponentBase which also defines it's own DI services container. Note I'm using Net6.0 here.
First the configuation data in appsettings.json of the web project.
{
"AzureData": {
"ConnectionString": "DefaultEndpointsProtocol=https;AccountName=MyApplocalsa;AccountKey=<truncated-account-key>;EndpointSuffix=core.windows.net",
"ContainerName": "Pictures"
},
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*"
}
Next define a data class to hold the configuration data
public class AzureData
{
public readonly Guid Id = Guid.NewGuid();
public string ConnectionString { get; set; } = string.Empty;
public string ContainerName { get; set; } = string.Empty;
}
Now register a configuration instance binding an AzureData instance against a section in the configuration file.
// Add services to the container.
builder.Services.AddRazorPages();
builder.Services.AddServerSideBlazor();
builder.Services.AddSingleton<WeatherForecastService>();
builder.Services.Configure<AzureData>(builder.Configuration.GetSection("AzureData"));
Finally the component.
Note:
We use IOptions<AzureData> to get the specific configuration instance, and Value to get the actual object.
AzureData is the same DI object, inside or outside the local service container. It's defined as a singleton.
#page "/di"
#inherits OwningComponentBase
#using Microsoft.Extensions.Options
<h3>DI Component</h3>
<div class="m-2 p-2">
Main Service Container <br />
Id: #AzureDataConfig?.Value.Id <br />
Connection String: #AzureDataConfig?.Value.ConnectionString
</div>
<div class="m-2 p-2">
Component Service Container <br />
Id:#azureData?.Value.Id <br />
Connection String: #azureData?.Value.ConnectionString
</div>
#code {
[Inject] private IOptions<AzureData>? AzureDataConfig { get; set; }
private IOptions<AzureData>? azureData;
protected override void OnInitialized()
{
azureData = ScopedServices.GetService<IOptions<AzureData>>();
base.OnInitialized();
}
}
I finally figured out the answer to the question by looking at other modules in the solution.
Here is the updated code
namespace MyApp
{
[DependsOn(
typeof(MyAppDomainModule),
typeof(AbpAccountApplicationModule),
typeof(MyAppApplicationContractsModule),
typeof(AbpIdentityApplicationModule),
typeof(AbpPermissionManagementApplicationModule),
typeof(AbpTenantManagementApplicationModule),
typeof(AbpFeatureManagementApplicationModule),
typeof(AbpSettingManagementApplicationModule),
typeof(AbpBlobStoringModule),
typeof(AbpBlobStoringAzureModule)
)]
public class MyAppApplicationModule : AbpModule
{
public override void ConfigureServices(ServiceConfigurationContext context)
{
var configuration = context.Services.GetConfiguration();
Configure<AbpAutoMapperOptions>(options =>
{
options.AddMaps<MyAppApplicationModule>();
});
Configure<AbpBlobStoringOptions>(options =>
{
options.Containers.ConfigureDefault(container =>
{
container.UseAzure(azure =>
{
azure.ConnectionString = configuration.GetSection("BlobStorage:ConnectionString").Value;
azure.ContainerName = configuration.GetSection("BlobStorage:ContainerName").Value;
azure.CreateContainerIfNotExists = true;
});
});
});
}
}
}
I needed to add the using
using Microsoft.Extensions.DependencyInjection;
I was able to get a reference to the configuration
var configuration = context.Services.GetConfiguration();
I updated the hard coded connection string with retrieving it from the configuration.
azure.ConnectionString = configuration.GetSection("BlobStorage:ConnectionString").Value;
azure.ContainerName = configuration.GetSection("BlobStorage:ContainerName").Value;
I updated the appsettings.json file in my Blazor app
"BlobStorage": {
"ConnectionString": "DefaultEndpointsProtocol=https;AccountName=myapplocalsa;AccountKey=<truncated>;EndpointSuffix=core.windows.net",
"ContainerName" : "Pictures"
}
That was it!
Thank you Joe for investing your time in providing answers to my question!
For others who might be looking for a solution to the same problem - I have a couple of things to add. I was using a separated tenant with separate Product.IdentityServer, Product.Web, and Product.HttpApi.Host projects.
The configuration I was trying to perform was for the AbpTwilioSmsModule and AbpBlobStoringModule. The values for these modules were hardcoded into my
Product.Domain.ProductDomainModule class.
// TODO - Need to configure this through appsettings.json - JLavallet 2022-02-10 12:23
Configure<AbpTwilioSmsOptions>(options =>
{
options.AccountSId = "yada-yada-yada";
options.AuthToken = "yada-yada-yada";
options.FromNumber = "+18885551212";
});
// TODO - Need to configure this through appsettings.json - JLavallet 2022-02-10 12:24
Configure<AbpBlobStoringOptions>(options =>
{
options.Containers.ConfigureDefault(container =>
{
container.IsMultiTenant = true;
container.UseFileSystem(fileSystem => { fileSystem.BasePath = #"D:\Product\DevFiles"; });
});
});
I modified that code to try and read from the context just like the OP. I wasn't sure what property of the context contained the configuration. I tried all kind of things and set breakpoints to try and find the configuration object in the context without success.
Configure<AbpTwilioSmsOptions>(options =>
{
options.AccountSId = context.WHAT?["AbpTwilioSms:AccountSId"];
options.AuthToken = context.WHAT?["AbpTwilioSms:AuthToken"];
options.FromNumber = context.WHAT?["AbpTwilioSms:FromNumber"];
});
Configure<AbpBlobStoringOptions>(options =>
{
options.Containers.ConfigureDefault(container =>
{
container.IsMultiTenant = Convert.ToBoolean(context.WHAT?["AbpBlobStoring:IsMultiTenant"]);
container.UseFileSystem(fileSystem =>
{
fileSystem.BasePath = context.WHAT?["AbpBlobStoring:FileSystemBasePath"];
});
});
});
At that point I came across this post and found how to get the configuration object out of the context.
Not all was well, however…
For the longest time I could not understand why I could not read my appsettings.json configuration information that I had placed in the Product.HttpApi.Host root folder. I was able to get to the configuration object but my values were still null.
I then had the thought that I should add an appsettings.json file to my Product.Domain root folder; surprisingly that had no effect.
I finally came around to moving the service configuration code out of my Product.Domain.ProductDomainModule class and into my Product.HttpApi.Host.ProductHttpApiHostModule class and my Product.IdentityServer.ProductIdentityServerModule class.
[DependsOn(
typeof(ProductHttpApiModule),
typeof(AbpAutofacModule),
typeof(AbpCachingStackExchangeRedisModule),
typeof(AbpAspNetCoreMvcUiMultiTenancyModule),
typeof(AbpIdentityAspNetCoreModule),
typeof(ProductApplicationModule),
typeof(ProductEntityFrameworkCoreModule),
typeof(AbpSwashbuckleModule),
typeof(AbpAspNetCoreSerilogModule)
)]
// added by Jack
[DependsOn(typeof(AbpTwilioSmsModule))]
[DependsOn(typeof(AbpBlobStoringModule))]
[DependsOn(typeof(AbpBlobStoringFileSystemModule))]
public class ProductHttpApiHostModule : AbpModule
{
public override void ConfigureServices(ServiceConfigurationContext context)
{
var configuration = context.Services.GetConfiguration();
var hostingEnvironment = context.Services.GetHostingEnvironment();
ConfigureUrls(configuration);
ConfigureConventionalControllers();
ConfigureAuthentication(context, configuration);
ConfigureSwagger(context, configuration);
ConfigureCache(configuration);
ConfigureVirtualFileSystem(context);
ConfigureDataProtection(context, configuration, hostingEnvironment);
ConfigureCors(context, configuration);
ConfigureExternalProviders(context);
ConfigureHealthChecks(context);
ConfigureTenantResolver(context, configuration);
//added by Jack
ConfigureTwilioSms(configuration);
ConfigureBlobStoring(configuration);
}
private void ConfigureBlobStoring(IConfiguration configuration)
{
Configure<AbpBlobStoringOptions>(options =>
{
options.Containers.ConfigureDefault(container =>
{
container.IsMultiTenant = Convert.ToBoolean(configuration["AbpBlobStoring:IsMultiTenant"]);
container.UseFileSystem(fileSystem =>
{
fileSystem.BasePath = configuration["AbpBlobStoring:FileSystemBasePath"];
});
});
});
}
private void ConfigureTwilioSms(IConfiguration configuration)
{
Configure<AbpTwilioSmsOptions>(options =>
{
options.AccountSId = configuration["AbpTwilioSms:AccountSId"];
options.AuthToken = configuration["AbpTwilioSms:AuthToken"];
options.FromNumber = configuration["AbpTwilioSms:FromNumber"];
});
}
I then copied my configuration entries from the Product.HttpApi.Host\appsettings.json file into my Product.IdentityServer\appsettings.json file and everything worked beautifully.
{
...,
"AbpTwilioSms": {
"AccountSId": "yada-yada-yada",
"AuthToken": "yada-yada-yada",
"FromNumber": "+18885551212"
},
"AbpBlobStoring": {
"IsMultiTenant": true,
"FileSystemBasePath": "D:\\Product\\DevFiles\\"
}
}

WebAssembly's IAuthenticationTokenProvider crashes when requesting a token

I am trying to authenticate the user in my WASM Blazor app using google's OIDC.
I have managed to retrieve the token by following this article: https://learn.microsoft.com/en-gb/aspnet/core/blazor/security/webassembly/standalone-with-authentication-library?view=aspnetcore-3.1&tabs=visual-studio
I am trying to retrieve the AccessToken to pass it to the SignalR hub using the injected instance of IAccessTokenProvider when building an instance of HubConnection:
public RemoteCombatListener(ITokenCache tokenCache)
{
_connection = new HubConnectionBuilder()
.WithUrl("https://localhost:44364/combat", opts => {
opts.AccessTokenProvider = tokenCache.GetToken;
})
.Build();
}
Here is the implementation of my TokenCache:
public class TokenCache : ITokenCache
{
private readonly IAccessTokenProvider _tokenProvider;
private readonly NavigationManager _navManager;
public string CachedToken { get; private set; }
public TokenCache(IAccessTokenProvider tokenProvider, NavigationManager navManager)
{
_tokenProvider = tokenProvider;
_navManager = navManager;
}
public async Task<string> GetToken()
{
if (string.IsNullOrEmpty(CachedToken))
{
var requestedToken = await _tokenProvider.RequestAccessToken();
if (requestedToken.TryGetToken(out var accessToken))
{
CachedToken = accessToken.Value;
}
else
{
throw new AccessTokenNotAvailableException(_navManager, requestedToken, Enumerable.Empty<string>());
}
}
return CachedToken;
}
}
The problem I am facing right now is that when calling the _tokenProvider.RequestAccessToken() method, I get the following exception:
An exception occurred executing JS interop: The JSON value could not be converted to System.DateTimeOffset. Path: $.token.expires | LineNumber: 0 | BytePositionInLine: 80.. See InnerException for more details.
I am unable to figure out what is wrong with my setup as debugging stopped working for me randomly and the only option I have is Console.Log debugging.
It turns out that default configuration for the Oidc doesn't request access_token, only id_token. Had to add the following:
builder.Services.AddOidcAuthentication(options => {
// Rest of configs ...
options.ProviderOptions.ResponseType = "id_token token";
});

How does one transform SOAP EndpointAddress in .NET Core?

When connecting a SOAP service in .NET Core the Connected Service is shown as expected in the solution explorer
The ConnectedService.json does contain the definitions as supposed. I.e.
{
"ProviderId": "Microsoft.VisualStudio.ConnectedService.Wcf",
...
"ExtendedData": {
"Uri": "https://test.example.net/Service.svc",
"Namespace": "UserWebService",
"SelectedAccessLevelForGeneratedClass": "Public",
...
}
The Uri from ExtendedData ends up in the Reference.cs file
private static System.ServiceModel.EndpointAddress GetEndpointAddress(EndpointConfiguration endpointConfiguration)
{
if ((endpointConfiguration == EndpointConfiguration.WSHttpBinding_IAnvandareService))
{
return new System.ServiceModel.EndpointAddress("https://test.example.net/Service.svc");
}
throw new System.InvalidOperationException(string.Format("Could not find endpoint with name \'{0}\'.", endpointConfiguration));
}
If a deployment process looks like TEST > STAGING > PRODUCTION one might like to have corresponding endpoints. I.e. https://production.example.net/Service.svc.
We use Azure Devops for build and Azure Devops/Octopus Deploy for deployments
The solution (as I figured) was to change the endpoint address when you register the dependency i.e.
var environment = Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT");
services.AddTransient<IAnvandareService, AnvandareServiceClient>((ctx) => new AnvandareServiceClient()
{
Endpoint =
{
Address = new EndpointAddress($"https://{environment}.example.net/Service.svc")
}
});
This is just an expansion of the answer provided by Eric Herlitz. Primarily meant to show how to use your appsettings.json file to hold the value for the endpoint url.
You will need to add the different endpoints to your appsettings.{enviroment}.json files.
{
...
"ServiceEndpoint": "http://someservice/service1.asmx",
...
}
Then you will need to make sure your environment variable is updated when you publish to different environments. How to transform appsettings.json
In your startup class find the method ConfigureServices() and register your service for dependency injection
public void ConfigureServices(IServiceCollection services)
{
services.AddTransient<ADSoap, ADSoapClient>(fac =>
{
var endpoint = Configuration.GetValue<string>("ServiceEndpoint");
return new ADSoapClient(ADSoapClient.EndpointConfiguration.ADSoap12)
{
Endpoint =
{
Address = new EndpointAddress(new Uri(endpoint))
}
};
});
}
Then to consume the service in some class you can inject the service into the constructor:
public class ADProvider : BaseProvider, IADProvider
{
public ADSoap ADService { get; set; }
public ADProvider(IAPQ2WebApiHttpClient httpClient, IMemoryCache cache, ADSoap adClient) : base(httpClient, cache)
{
ADService = adClient;
}
}

Hangfire Dashboard .net core - authentication works locally, not on server

I've implemented authentication for Hangfire Dashboard as recommended (the Dashboard isn't viewable on the server without authentication).
It wasn't working on the server, so I just set Authorize to always be true:
In startup.cs:
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
//Other stuff here
app.UseHangfireDashboard("/hangfire", new DashboardOptions
{
Authorization = new[] { new MyAuthorizationFilter() }
});
}
In my own class:
/// <summary>
/// Used for Hangfire Dashboard only
/// </summary>
public class MyAuthorizationFilter : IDashboardAuthorizationFilter
{
public bool Authorize(DashboardContext context)
{
return true;
}
}
However, even this gets a 403 error returned - yet it works fine locally. The previous auth also works fine locally but not on the server.
I've searched Google but can't find anyone having this error - how can I fix it?
Thanks.
for worked like below:
class MyAuthorizationFilter : IDashboardAuthorizationFilter
{
public bool Authorize(DashboardContext context)
{
var httpContext = context.GetHttpContext();
// Allow all authenticated users to see the Dashboard (potentially dangerous).
return httpContext.User.Identity.IsAuthenticated;
}
}
And in startup method use as below:
app.UseAuthentication();
app.UseHangfireDashboard(options: new DashboardOptions()
{
Authorization = new IDashboardAuthorizationFilter[]
{
new MyAuthorizationFilter()
}
});
app.UseHangfireServer();
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
});