I already read the documentation you pointed to, but coming from ASP.NET Boilerplate and Castle Windsor, I'm struggling some with the change in terminology and the differences between the two approaches to dependency injection.
But, I think I have been able to figure out how it can work and it seems cleaner than what I had to do before.
In my Application project I have created this class that inherits from Serilog's ILogEventEnricher:
Then, in Program.cs in my Blazor project I can add this to the configuration of Serilog and it seems to work:
In ASP.NET Boilerplate this would not work as shown above because I had to inject the AbpSession service in my class using some way other than constructor dependency injection or property dependency injection.
The only solution was to directly reference the service from the IOC container.
Glad that seems no longer necessary.
Check the docs before asking a question: https://docs.abp.io/en/commercial/latest/ Check the samples, to see the basic tasks: https://docs.abp.io/en/commercial/latest/samples/index The exact solution to your question may have been answered before, please use the search on the homepage.
If you're creating a bug/problem report, please include followings:
We are in the process of re-engineering our ASP.NET Zero application in ABP Commercial.
In the ASPNET Zero version, we had a custom class to enrich our Serilog-based event logs. It had to inherit from ILogEventEnricher, provided by Serilog.
Because of the way ILogEventEnricher works, we couldn't inject any dependencies in the constructor for the class. We had to directly reference the needed service from the IOC container, like this:
var abpSession = Abp.Dependency.SingletonDependency.Instance;
We understand this is something we shouldn't normally do, but we had no choice due to the constraints of Serilog.
How would we do this (directly reference a service object in the IOC container) in ABP Framework? I have looked at the documentation and search the forum, but haven't found any instructions.
Thanks - that solved the problem.
I had been following this page of documentation:
https://docs.abp.io/en/abp/latest/Background-Jobs-Hangfire
I'd say it should be updated to add this requirement.
Jeff
I found another post in this forum that said logging in to Abp with the Abp CLI would remove it. I did that and, sure enough, the message went away in the logs.
I guess my question at this point is what will happen in Production? There won't be a way to log into the Abp CLI in production.
Getting the same error over and over again in my logs when starting my Blazor app. The app seems to run fine, but this error doesn't look great in the logs.
Is it anything to be concered about?
Check the docs before asking a question: https://docs.abp.io/en/commercial/latest/ Check the samples, to see the basic tasks: https://docs.abp.io/en/commercial/latest/samples/index The exact solution to your question may have been answered before, please use the search on the homepage.
If you're creating a bug/problem report, please include followings:
When I run my application in dev and try to browse to the Hangfire dashboard I get a 404 page not found:
Here is how I have Hangfire configured in my Blazor project's module:
using System; using System.IO; using System.Threading.Tasks; using Blazorise.Bootstrap5; using Blazorise.Icons.FontAwesome; using Microsoft.AspNetCore.Authentication.Google; using Microsoft.AspNetCore.Authentication.JwtBearer; using Microsoft.AspNetCore.Authentication.MicrosoftAccount; using Microsoft.AspNetCore.Authentication.Twitter; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Pol.Blazor.Menus; using Pol.EntityFrameworkCore; using Pol.Localization; using Pol.MultiTenancy; using Microsoft.OpenApi.Models; using Pol.Blazor.Components.Layout; using Volo.Abp; using Volo.Abp.Account.Pro.Admin.Blazor.Server; using Volo.Abp.Account.Pro.Public.Blazor.Server; using Volo.Abp.Account.Public.Web; using Volo.Abp.Account.Public.Web.ExternalProviders; using Volo.Abp.Account.Web; using Volo.Abp.Account.Public.Web.Impersonation; using Volo.Abp.AspNetCore.Authentication.JwtBearer; using Volo.Abp.AspNetCore.Components.Server.LeptonTheme; using Volo.Abp.AspNetCore.Components.Server.LeptonTheme.Bundling; using Volo.Abp.AspNetCore.Components.Web.Theming.Routing; using Volo.Abp.AspNetCore.Mvc; using Volo.Abp.AspNetCore.Mvc.Localization; using Volo.Abp.AspNetCore.Mvc.UI.Bundling; using Volo.Abp.AspNetCore.Mvc.UI.Theme.Lepton; using Volo.Abp.AspNetCore.Mvc.UI.Theme.Lepton.Bundling; using Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared; using Volo.Abp.AspNetCore.Serilog; using Volo.Abp.AuditLogging.Blazor.Server; using Volo.Abp.Autofac; using Volo.Abp.AutoMapper; using Volo.Abp.Gdpr.Blazor.Server; using Volo.Abp.Identity; using Volo.Abp.Identity.Pro.Blazor; using Volo.Abp.Identity.Pro.Blazor.Server; using Volo.Abp.IdentityServer.Blazor.Server; using Volo.Abp.LanguageManagement.Blazor.Server; using Volo.Abp.LeptonTheme.Management.Blazor.Server; using Volo.Abp.Localization; using Volo.Abp.Modularity; using Volo.Abp.Swashbuckle; using Volo.Abp.TextTemplateManagement.Blazor.Server; using Volo.Abp.UI.Navigation; using Volo.Abp.UI.Navigation.Urls; using Volo.Abp.VirtualFileSystem; using Volo.Saas.Host; using Volo.Saas.Host.Blazor; using Volo.Saas.Host.Blazor.Server; using Volo.Abp.BackgroundJobs.Hangfire; using Hangfire;
namespace Pol.Blazor;
[DependsOn( typeof(PolApplicationModule), typeof(PolEntityFrameworkCoreModule), typeof(PolHttpApiModule), typeof(AbpAspNetCoreMvcUiLeptonThemeModule), typeof(AbpAutofacModule), typeof(AbpSwashbuckleModule), typeof(AbpAccountPublicWebImpersonationModule), typeof(AbpAspNetCoreSerilogModule), typeof(AbpAspNetCoreComponentsServerLeptonThemeModule), typeof(AbpAccountPublicWebIdentityServerModule), typeof(AbpAccountPublicBlazorServerModule), typeof(AbpAccountAdminBlazorServerModule), typeof(AbpAuditLoggingBlazorServerModule), typeof(AbpIdentityProBlazorServerModule), typeof(LeptonThemeManagementBlazorServerModule), typeof(AbpIdentityServerBlazorServerModule), typeof(LanguageManagementBlazorServerModule), typeof(SaasHostBlazorServerModule), typeof(TextTemplateManagementBlazorServerModule), typeof(AbpGdprBlazorServerModule), typeof(AbpBackgroundJobsHangfireModule) )]
public class PolBlazorModule : AbpModule
{ public override void PreConfigureServices(ServiceConfigurationContext context) { context.Services.PreConfigure(options => { options.AddAssemblyResource( typeof(PolResource), typeof(PolDomainModule).Assembly, typeof(PolDomainSharedModule).Assembly, typeof(PolApplicationModule).Assembly, typeof(PolApplicationContractsModule).Assembly, typeof(PolBlazorModule).Assembly ); }); }
public override void ConfigureServices(ServiceConfigurationContext context)
{
var hostingEnvironment = context.Services.GetHostingEnvironment();
var configuration = context.Services.GetConfiguration();
ConfigureUrls(configuration);
ConfigureBundles();
ConfigureAuthentication(context, configuration);
ConfigureImpersonation(context, configuration);
ConfigureAutoMapper();
ConfigureVirtualFileSystem(hostingEnvironment);
ConfigureLocalizationServices();
ConfigureSwaggerServices(context.Services);
ConfigureExternalProviders(context, configuration);
ConfigureAutoApiControllers();
ConfigureBlazorise(context);
ConfigureRouter(context);
ConfigureMenu(context);
ConfigureLeptonTheme();
ConfigureHangfire(context, configuration);
}
private void ConfigureHangfire(ServiceConfigurationContext context, IConfiguration configuration)
{
context.Services.AddHangfire(config =>
{
config.UseSqlServerStorage(configuration.GetConnectionString("Hangfire"));
});
}
private void ConfigureUrls(IConfiguration configuration)
{
Configure<AppUrlOptions>(options =>
{
options.Applications["MVC"].RootUrl = configuration["App:SelfUrl"];
options.RedirectAllowedUrls.AddRange(configuration["App:RedirectAllowedUrls"].Split(','));
});
}
private void ConfigureBundles()
{
Configure<AbpBundlingOptions>(options =>
{
// MVC UI
options.StyleBundles.Configure(
LeptonThemeBundles.Styles.Global,
bundle =>
{
bundle.AddFiles("/global-styles.css");
}
);
// Blazor UI
options.StyleBundles.Configure(
BlazorLeptonThemeBundles.Styles.Global,
bundle =>
{
bundle.AddFiles("/blazor-global-styles.css");
//You can remove the following line if you don't use Blazor CSS isolation for components
bundle.AddFiles("/Pol.Blazor.styles.css");
}
);
});
}
private void ConfigureAuthentication(ServiceConfigurationContext context, IConfiguration configuration)
{
context.Services.AddAuthentication()
.AddJwtBearer(options =>
{
options.Authority = configuration["AuthServer:Authority"];
options.RequireHttpsMetadata = Convert.ToBoolean(configuration["AuthServer:RequireHttpsMetadata"]);
options.Audience = "Pol";
});
}
private void ConfigureImpersonation(ServiceConfigurationContext context, IConfiguration configuration)
{
context.Services.Configure<SaasHostBlazorOptions>(options =>
{
options.EnableTenantImpersonation = true;
});
context.Services.Configure<AbpIdentityProBlazorOptions>(options =>
{
options.EnableUserImpersonation = true;
});
context.Services.Configure<AbpAccountOptions>(options =>
{
options.TenantAdminUserName = "admin";
options.ImpersonationTenantPermission = SaasHostPermissions.Tenants.Impersonation;
options.ImpersonationUserPermission = IdentityPermissions.Users.Impersonation;
});
}
private void ConfigureVirtualFileSystem(IWebHostEnvironment hostingEnvironment)
{
if (hostingEnvironment.IsDevelopment())
{
Configure<AbpVirtualFileSystemOptions>(options =>
{
options.FileSets.ReplaceEmbeddedByPhysical<PolDomainSharedModule>(Path.Combine(hostingEnvironment.ContentRootPath, $"..{Path.DirectorySeparatorChar}Pol.Domain.Shared"));
options.FileSets.ReplaceEmbeddedByPhysical<PolDomainModule>(Path.Combine(hostingEnvironment.ContentRootPath, $"..{Path.DirectorySeparatorChar}Pol.Domain"));
options.FileSets.ReplaceEmbeddedByPhysical<PolApplicationContractsModule>(Path.Combine(hostingEnvironment.ContentRootPath, $"..{Path.DirectorySeparatorChar}Pol.Application.Contracts"));
options.FileSets.ReplaceEmbeddedByPhysical<PolApplicationModule>(Path.Combine(hostingEnvironment.ContentRootPath, $"..{Path.DirectorySeparatorChar}Pol.Application"));
options.FileSets.ReplaceEmbeddedByPhysical<PolBlazorModule>(hostingEnvironment.ContentRootPath);
});
}
}
private void ConfigureSwaggerServices(IServiceCollection services)
{
services.AddAbpSwaggerGen(
options =>
{
options.SwaggerDoc("v1", new OpenApiInfo { Title = "Pol API", Version = "v1" });
options.DocInclusionPredicate((docName, description) => true);
options.CustomSchemaIds(type => type.FullName);
}
);
}
private void ConfigureExternalProviders(ServiceConfigurationContext context, IConfiguration configuration)
{
context.Services.AddAuthentication()
.AddGoogle(GoogleDefaults.AuthenticationScheme, _ => {})
.WithDynamicOptions<GoogleOptions, GoogleHandler>(
GoogleDefaults.AuthenticationScheme,
options =>
{
options.WithProperty(x => x.ClientId);
options.WithProperty(x => x.ClientSecret, isSecret: true);
}
)
.AddMicrosoftAccount(MicrosoftAccountDefaults.AuthenticationScheme, options =>
{
//Personal Microsoft accounts as an example.
options.AuthorizationEndpoint = "https://login.microsoftonline.com/consumers/oauth2/v2.0/authorize";
options.TokenEndpoint = "https://login.microsoftonline.com/consumers/oauth2/v2.0/token";
})
.WithDynamicOptions<MicrosoftAccountOptions, MicrosoftAccountHandler>(
MicrosoftAccountDefaults.AuthenticationScheme,
options =>
{
options.WithProperty(x => x.ClientId);
options.WithProperty(x => x.ClientSecret, isSecret: true);
}
)
.AddTwitter(TwitterDefaults.AuthenticationScheme, options => options.RetrieveUserDetails = true)
.WithDynamicOptions<TwitterOptions, TwitterHandler>(
TwitterDefaults.AuthenticationScheme,
options =>
{
options.WithProperty(x => x.ConsumerKey);
options.WithProperty(x => x.ConsumerSecret, isSecret: true);
}
);
}
private void ConfigureLocalizationServices()
{
Configure<AbpLocalizationOptions>(options =>
{
options.Languages.Add(new LanguageInfo("ar", "ar", "العربية", "ae"));
options.Languages.Add(new LanguageInfo("cs", "cs", "Čeština"));
options.Languages.Add(new LanguageInfo("en", "en", "English"));
options.Languages.Add(new LanguageInfo("en-GB", "en-GB", "English (UK)"));
options.Languages.Add(new LanguageInfo("hu", "hu", "Magyar"));
options.Languages.Add(new LanguageInfo("fi", "fi", "Finnish", "fi"));
options.Languages.Add(new LanguageInfo("fr", "fr", "Français", "fr"));
options.Languages.Add(new LanguageInfo("hi", "hi", "Hindi", "in"));
options.Languages.Add(new LanguageInfo("it", "it", "Italiano", "it"));
options.Languages.Add(new LanguageInfo("pt-BR", "pt-BR", "Português"));
options.Languages.Add(new LanguageInfo("ru", "ru", "Русский", "ru"));
options.Languages.Add(new LanguageInfo("sk", "sk", "Slovak", "sk"));
options.Languages.Add(new LanguageInfo("tr", "tr", "Türkçe"));
options.Languages.Add(new LanguageInfo("zh-Hans", "zh-Hans", "简体中文"));
options.Languages.Add(new LanguageInfo("zh-Hant", "zh-Hant", "繁體中文"));
options.Languages.Add(new LanguageInfo("de-DE", "de-DE", "Deutsch", "de"));
options.Languages.Add(new LanguageInfo("es", "es", "Español"));
});
}
private void ConfigureBlazorise(ServiceConfigurationContext context)
{
context.Services
.AddBootstrap5Providers()
.AddFontAwesomeIcons();
}
private void ConfigureMenu(ServiceConfigurationContext context)
{
Configure<AbpNavigationOptions>(options =>
{
options.MenuContributors.Add(new PolMenuContributor());
});
}
private void ConfigureLeptonTheme()
{
Configure<Volo.Abp.AspNetCore.Components.Web.LeptonTheme.LeptonThemeOptions>(options =>
{
options.FooterComponent = typeof(MainFooterComponent);
});
}
private void ConfigureRouter(ServiceConfigurationContext context)
{
Configure<AbpRouterOptions>(options =>
{
options.AppAssembly = typeof(PolBlazorModule).Assembly;
});
}
private void ConfigureAutoApiControllers()
{
Configure<AbpAspNetCoreMvcOptions>(options =>
{
options.ConventionalControllers.Create(typeof(PolApplicationModule).Assembly);
});
}
private void ConfigureAutoMapper()
{
Configure<AbpAutoMapperOptions>(options =>
{
options.AddMaps<PolBlazorModule>();
});
}
public override void OnApplicationInitialization(ApplicationInitializationContext context)
{
var env = context.GetEnvironment();
var app = context.GetApplicationBuilder();
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseAbpRequestLocalization();
if (!env.IsDevelopment())
{
app.UseErrorPage();
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseCorrelationId();
app.UseStaticFiles();
app.UseRouting();
app.UseAuthentication();
app.UseJwtTokenMiddleware();
if (MultiTenancyConsts.IsEnabled)
{
app.UseMultiTenancy();
}
app.UseUnitOfWork();
app.UseIdentityServer();
app.UseAuthorization();
app.UseSwagger();
app.UseAbpSwaggerUI(options =>
{
options.SwaggerEndpoint("/swagger/v1/swagger.json", "Pol API");
});
app.UseAuditing();
app.UseAbpSerilogEnrichers();
app.UseConfiguredEndpoints();
app.UseHangfireDashboard("/hangfire");
}
}
Also, this is what shows up in the application logs:
My only issue with this method is that it doesn't seem to truly wait for the method to finish, or the Dropdownlist is not reloading after the methods return. After a user selects a previously created set of credentials in a dropdown, the programs takes the selected credentials and attempts to grab some information from Sharepoint Online and then populate the next dropdown. When I was able to use the await keyword it would populate correctly, but using Task.Run because await isn't possible in the setter, it doesn't populate on the first credentials selection. It populates if you select the credential a second time, but not the first. Is there any way to reload a dropdown other than StateHasChanged method as this is what Ihave used so far.
Seems like it needed to use InvokeAsync(StateHasChanged) to actually have the Dropdown reloaded at the right time.
This is a Blazorise limitation. There is no event or async callback for selection change.
But you can still use your async operation with
Task.Run()
as a workaround.public partial class Index { private Guid selectedIndexItemId; public Guid SelectedIndexItemId { get => selectedIndexItemId; set { selectedIndexItemId = value; Task.Run(OnDropdownSelected); // 👈 } } public async Task OnDropdownSelected() // 👈 { await Task.Delay(1000); Logger.LogInformation("OnDropdownSelected!"); } }
My only issue with this method is that it doesn't seem to truly wait for the method to finish, or the Dropdownlist is not reloading after the methods return. After a user selects a previously created set of credentials in a dropdown, the programs takes the selected credentials and attempts to grab some information from Sharepoint Online and then populate the next dropdown. When I was able to use the await keyword it would populate correctly, but using Task.Run because await isn't possible in the setter, it doesn't populate on the first credentials selection. It populates if you select the credential a second time, but not the first. Is there any way to reload a dropdown other than StateHasChanged method as this is what Ihave used so far.
You can use
SelectedValueChanged
event and manage current selected item manually instead of using binding.Or, You can just use a regular encapsulation over your property. You can use setter of the
selectedDropValueSPO
property.I have an example for that:
Index.razor
<Card> <CardBody> <DropdownList @ref="dropdownRef" TItem="IndexItem" TValue="Guid" Data="IndexItems" @bind-SelectedValue="SelectedIndexItemId" TextField="@((item)=>item.Name)" ValueField="@((item)=>item.Id)" >Select Credential</DropdownList> </CardBody> <CardFooter> <p> @SelectedIndexItemId </p> </CardFooter> </Card>
Index.razor.cs
public partial class Index { protected DropdownList<IndexItem, Guid> dropdownRef; private Guid selectedIndexItemId; public List<IndexItem> IndexItems { get; set; } = new List<IndexItem> { new IndexItem(Guid.NewGuid(), "Item 1"), new IndexItem(Guid.NewGuid(), "Item 2"), new IndexItem(Guid.NewGuid(), "Item 3"), new IndexItem(Guid.NewGuid(), "Item 4"), new IndexItem(Guid.NewGuid(), "Item 5"), }; public Guid SelectedIndexItemId { get => selectedIndexItemId; set { selectedIndexItemId = value; Logger.LogInformation("SelectedIndexItemIdChanged!"); } } }
I actually do need to be able to using the await keyword in the "onSelectedValueChange" event, or in this case the setter. Any way that I can still do that with this method?
I guess that's more or less what you answered last time. Thanks for your help