No manifests exist for the current culture using IStringLocalizer - c#

I'm developing backend application using .NET core 3.1. I moved CommonResource from my old project (.NET core 2.1) where works fine.
I want to obtain value for key in specified culture.
Implementation of CommonResource:
public class CommonResource : ICommonResource
{
private readonly IStringLocalizer<SharedResource> _localizer;
public CommonResource(
IStringLocalizer<SharedResource> localizer)
{
_localizer = localizer;
}
#region ICommonResource Members
#region General
public string AppName(
string language)
=> GetString(language, nameof(AppName));
#endregion
#endregion
/// <summary>
/// Dispose of class and parent classes
/// </summary>
public void Dispose()
=> GC.SuppressFinalize(this);
private string GetString(
string language,
string name)
{
var currentCulture = CultureInfo.CurrentCulture;
CultureInfo.CurrentCulture = new CultureInfo(language);
var value = _localizer[name];
CultureInfo.CurrentCulture = currentCulture;
// TEST - check all string
//var result = _localizer.GetAllStrings();
return value;
}
}
LocalizationExtensions with AddDefaultLocalization method:
public static class LocalizationExtensions
{
private const bool AcceptLanguageHeaderRequestCultureProvider = false;
private static readonly CultureInfo CultureInfoEnUs = new CultureInfo("en-US");
private static readonly CultureInfo CultureInfoPlPl = new CultureInfo("pl-PL");
private static readonly CultureInfo DefaultRequestCulture = CultureInfoEnUs;
private static readonly CultureInfo[] SupportedCultures =
{
CultureInfoEnUs,
CultureInfoPlPl
};
public static void AddDefaultLocalization(
this IServiceCollection serviceCollection)
{
serviceCollection.AddLocalization();
serviceCollection.Configure<RequestLocalizationOptions>(
options =>
{
options.DefaultRequestCulture = new RequestCulture(DefaultRequestCulture, DefaultRequestCulture);
options.SupportedCultures = SupportedCultures;
if (!AcceptLanguageHeaderRequestCultureProvider)
{
//https://stackoverflow.com/questions/44480759/asp-net-core-default-language-is-always-english
options.RequestCultureProviders = new List<IRequestCultureProvider>
{
new QueryStringRequestCultureProvider(),
new CookieRequestCultureProvider()
};
}
});
}
}
My SharedResources are in Resources path (Build Action: Embedded resource and Access Modifier: No code generation).
Unfortunately invoke _localizer.GetAllStrings(); generates exception "No manifests exist for the current culture".
I tried many solutions e.g. move SharedResources to root path or install extra nuget package, but nothing helped.
Do you have any idea why in new version .net core this problem exists?
Thanks for help!

Related

Place order function in NopCommerce 4.5

and I want to create a custom controller with a place order function.
I'm using the below code:
private async Task<Order> PlaceOrder(IList<ShoppingCartItem> cart)
{
var _orderProcessingService = EngineContext.Current.Resolve<IOrderProcessingService>();
var _genericAttributeService = EngineContext.Current.Resolve<IGenericAttributeService>();
var processPaymentRequest = HttpContext.Session.Get<ProcessPaymentRequest>("OrderPaymentInfo") ?? new ProcessPaymentRequest();
_paymentService.GenerateOrderGuid(processPaymentRequest);
processPaymentRequest.StoreId = (await _storeContext.GetCurrentStoreAsync()).Id;
processPaymentRequest.CustomerId = (await _workContext.GetCurrentCustomerAsync()).Id;
processPaymentRequest.PaymentMethodSystemName = await _genericAttributeService.GetAttributeAsync<string>(await _workContext.GetCurrentCustomerAsync(),
NopCustomerDefaults.SelectedPaymentMethodAttribute, (await _storeContext.GetCurrentStoreAsync()).Id);
HttpContext.Session.Set<ProcessPaymentRequest>("OrderPaymentInfo", processPaymentRequest);
var placeOrderResult = await _orderProcessingService.PlaceOrderAsync(processPaymentRequest);
if (placeOrderResult.Success)
{
HttpContext.Session.Set<ProcessPaymentRequest>("OrderPaymentInfo", null);
var postProcessPaymentRequest = new PostProcessPaymentRequest
{
Order = placeOrderResult.PlacedOrder
};
await _paymentService.PostProcessPaymentAsync(postProcessPaymentRequest);
return postProcessPaymentRequest.Order;
}
else
{
return null;
}
}
But where can call the function to place an order and clear the cart?
I'm working on nopcommerce 4.5
You can override checkout controller and action method for confirm order or simply override PlaceOrder method from OrderProcessingService and do you custom changes.
As you are working on the custom plugin so the better way is to override the PlaceOrderAsync method.
Below is the code for override OrderProcessingService service from the plugin. I consider NivoSlider for example.
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Threading.Tasks;
using Nop.Core;
using Nop.Core.Domain.Catalog;
using Nop.Core.Domain.Common;
using Nop.Core.Domain.Customers;
using Nop.Core.Domain.Directory;
using Nop.Core.Domain.Discounts;
using Nop.Core.Domain.Localization;
using Nop.Core.Domain.Logging;
using Nop.Core.Domain.Orders;
using Nop.Core.Domain.Payments;
using Nop.Core.Domain.Shipping;
using Nop.Core.Domain.Tax;
using Nop.Core.Domain.Vendors;
using Nop.Core.Events;
using Nop.Services.Affiliates;
using Nop.Services.Catalog;
using Nop.Services.Common;
using Nop.Services.Customers;
using Nop.Services.Directory;
using Nop.Services.Discounts;
using Nop.Services.Localization;
using Nop.Services.Logging;
using Nop.Services.Messages;
using Nop.Services.Orders;
using Nop.Services.Payments;
using Nop.Services.Security;
using Nop.Services.Shipping;
using Nop.Services.Stores;
using Nop.Services.Tax;
using Nop.Services.Vendors;
namespace Nop.Plugin.Widgets.NivoSlider.Services
{
/// <summary>
/// Order processing service
/// </summary>
public partial class CustomOrderProcessingService : OrderProcessingService , IOrderProcessingService
{
#region Fields
private readonly CurrencySettings _currencySettings;
private readonly IAddressService _addressService;
private readonly IAffiliateService _affiliateService;
private readonly ICheckoutAttributeFormatter _checkoutAttributeFormatter;
private readonly ICountryService _countryService;
private readonly ICurrencyService _currencyService;
private readonly ICustomerActivityService _customerActivityService;
private readonly ICustomerService _customerService;
private readonly ICustomNumberFormatter _customNumberFormatter;
private readonly IDiscountService _discountService;
private readonly IEncryptionService _encryptionService;
private readonly IEventPublisher _eventPublisher;
private readonly IGenericAttributeService _genericAttributeService;
private readonly IGiftCardService _giftCardService;
private readonly ILanguageService _languageService;
private readonly ILocalizationService _localizationService;
private readonly ILogger _logger;
private readonly IOrderService _orderService;
private readonly IOrderTotalCalculationService _orderTotalCalculationService;
private readonly IPaymentPluginManager _paymentPluginManager;
private readonly IPaymentService _paymentService;
private readonly IPdfService _pdfService;
private readonly IPriceCalculationService _priceCalculationService;
private readonly IPriceFormatter _priceFormatter;
private readonly IProductAttributeFormatter _productAttributeFormatter;
private readonly IProductAttributeParser _productAttributeParser;
private readonly IProductService _productService;
private readonly IReturnRequestService _returnRequestService;
private readonly IRewardPointService _rewardPointService;
private readonly IShipmentService _shipmentService;
private readonly IShippingService _shippingService;
private readonly IShoppingCartService _shoppingCartService;
private readonly IStateProvinceService _stateProvinceService;
private readonly IStoreService _storeService;
private readonly ITaxService _taxService;
private readonly IVendorService _vendorService;
private readonly IWebHelper _webHelper;
private readonly IWorkContext _workContext;
private readonly IWorkflowMessageService _workflowMessageService;
private readonly LocalizationSettings _localizationSettings;
private readonly OrderSettings _orderSettings;
private readonly PaymentSettings _paymentSettings;
private readonly RewardPointsSettings _rewardPointsSettings;
private readonly ShippingSettings _shippingSettings;
private readonly TaxSettings _taxSettings;
#endregion
#region Ctor
public CustomOrderProcessingService(CurrencySettings currencySettings,
IAddressService addressService,
IAffiliateService affiliateService,
ICheckoutAttributeFormatter checkoutAttributeFormatter,
ICountryService countryService,
ICurrencyService currencyService,
ICustomerActivityService customerActivityService,
ICustomerService customerService,
ICustomNumberFormatter customNumberFormatter,
IDiscountService discountService,
IEncryptionService encryptionService,
IEventPublisher eventPublisher,
IGenericAttributeService genericAttributeService,
IGiftCardService giftCardService,
ILanguageService languageService,
ILocalizationService localizationService,
ILogger logger,
IOrderService orderService,
IOrderTotalCalculationService orderTotalCalculationService,
IPaymentPluginManager paymentPluginManager,
IPaymentService paymentService,
IPdfService pdfService,
IPriceCalculationService priceCalculationService,
IPriceFormatter priceFormatter,
IProductAttributeFormatter productAttributeFormatter,
IProductAttributeParser productAttributeParser,
IProductService productService,
IReturnRequestService returnRequestService,
IRewardPointService rewardPointService,
IShipmentService shipmentService,
IShippingService shippingService,
IShoppingCartService shoppingCartService,
IStateProvinceService stateProvinceService,
IStoreService storeService,
ITaxService taxService,
IVendorService vendorService,
IWebHelper webHelper,
IWorkContext workContext,
IWorkflowMessageService workflowMessageService,
LocalizationSettings localizationSettings,
OrderSettings orderSettings,
PaymentSettings paymentSettings,
RewardPointsSettings rewardPointsSettings,
ShippingSettings shippingSettings,
TaxSettings taxSettings) :base(currencySettings,
addressService,
affiliateService,
checkoutAttributeFormatter,
countryService,
currencyService,
customerActivityService,
customerService,
customNumberFormatter,
discountService,
encryptionService,
eventPublisher,
genericAttributeService,
giftCardService,
languageService,
localizationService,
logger,
orderService,
orderTotalCalculationService,
paymentPluginManager,
paymentService,
pdfService,
priceCalculationService,
priceFormatter,
productAttributeFormatter,
productAttributeParser,
productService,
returnRequestService,
rewardPointService,
shipmentService,
shippingService,
shoppingCartService,
stateProvinceService,
storeService,
taxService,
vendorService,
webHelper,
workContext,
workflowMessageService,
localizationSettings,
orderSettings,
paymentSettings,
rewardPointsSettings,
shippingSettings,
taxSettings)
{
_currencySettings = currencySettings;
_addressService = addressService;
_affiliateService = affiliateService;
_checkoutAttributeFormatter = checkoutAttributeFormatter;
_countryService = countryService;
_currencyService = currencyService;
_customerActivityService = customerActivityService;
_customerService = customerService;
_customNumberFormatter = customNumberFormatter;
_discountService = discountService;
_encryptionService = encryptionService;
_eventPublisher = eventPublisher;
_genericAttributeService = genericAttributeService;
_giftCardService = giftCardService;
_languageService = languageService;
_localizationService = localizationService;
_logger = logger;
_orderService = orderService;
_orderTotalCalculationService = orderTotalCalculationService;
_paymentPluginManager = paymentPluginManager;
_paymentService = paymentService;
_pdfService = pdfService;
_priceCalculationService = priceCalculationService;
_priceFormatter = priceFormatter;
_productAttributeFormatter = productAttributeFormatter;
_productAttributeParser = productAttributeParser;
_productService = productService;
_returnRequestService = returnRequestService;
_rewardPointService = rewardPointService;
_shipmentService = shipmentService;
_shippingService = shippingService;
_shoppingCartService = shoppingCartService;
_stateProvinceService = stateProvinceService;
_storeService = storeService;
_taxService = taxService;
_vendorService = vendorService;
_webHelper = webHelper;
_workContext = workContext;
_workflowMessageService = workflowMessageService;
_localizationSettings = localizationSettings;
_orderSettings = orderSettings;
_paymentSettings = paymentSettings;
_rewardPointsSettings = rewardPointsSettings;
_shippingSettings = shippingSettings;
_taxSettings = taxSettings;
}
#endregion
#region Methods
public override async Task<PlaceOrderResult> PlaceOrderAsync(ProcessPaymentRequest processPaymentRequest)
{
if (processPaymentRequest == null)
throw new ArgumentNullException(nameof(processPaymentRequest));
var result = new PlaceOrderResult();
try
{
if (processPaymentRequest.OrderGuid == Guid.Empty)
throw new Exception("Order GUID is not generated");
//prepare order details
var details = await PreparePlaceOrderDetailsAsync(processPaymentRequest);
var processPaymentResult = await GetProcessPaymentResultAsync(processPaymentRequest, details);
if (processPaymentResult == null)
throw new NopException("processPaymentResult is not available");
if (processPaymentResult.Success)
{
var order = await SaveOrderDetailsAsync(processPaymentRequest, processPaymentResult, details);
result.PlacedOrder = order;
//move shopping cart items to order items
await MoveShoppingCartItemsToOrderItemsAsync(details, order);
//discount usage history
await SaveDiscountUsageHistoryAsync(details, order);
//gift card usage history
await SaveGiftCardUsageHistoryAsync(details, order);
//recurring orders
if (details.IsRecurringShoppingCart)
await CreateFirstRecurringPaymentAsync(processPaymentRequest, order);
//notifications
await SendNotificationsAndSaveNotesAsync(order);
//reset checkout data
await _customerService.ResetCheckoutDataAsync(details.Customer, processPaymentRequest.StoreId, clearCouponCodes: true, clearCheckoutAttributes: true);
await _customerActivityService.InsertActivityAsync("PublicStore.PlaceOrder",
string.Format(await _localizationService.GetResourceAsync("ActivityLog.PublicStore.PlaceOrder"), order.Id), order);
//raise event
await _eventPublisher.PublishAsync(new OrderPlacedEvent(order));
//check order status
await CheckOrderStatusAsync(order);
if (order.PaymentStatus == PaymentStatus.Paid)
await ProcessOrderPaidAsync(order);
}
else
foreach (var paymentError in processPaymentResult.Errors)
result.AddError(string.Format(await _localizationService.GetResourceAsync("Checkout.PaymentError"), paymentError));
}
catch (Exception exc)
{
await _logger.ErrorAsync(exc.Message, exc);
result.AddError(exc.Message);
}
if (result.Success)
return result;
//log errors
var logError = result.Errors.Aggregate("Error while placing order. ",
(current, next) => $"{current}Error {result.Errors.IndexOf(next) + 1}: {next}. ");
var customer = await _customerService.GetCustomerByIdAsync(processPaymentRequest.CustomerId);
await _logger.ErrorAsync(logError, customer: customer);
return result;
}
#endregion
}
}
Then the DI for the custom service at startup.
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Nop.Core.Infrastructure;
using Nop.Plugin.Widgets.NivoSlider.Services;
using Nop.Services.Messages;
using Nop.Services.Orders;
using Nop.Web.Framework.Infrastructure.Extensions;
namespace Nop.Plugin.Widgets.NivoSlider.Infrastructure
{
/// <summary>
/// Represents object for the configuring services on application startup
/// </summary>
public class NopStartup : INopStartup
{
/// <summary>
/// Add and configure any of the middleware
/// </summary>
/// <param name="services">Collection of service descriptors</param>
/// <param name="configuration">Configuration of the application</param>
public void ConfigureServices(IServiceCollection services, IConfiguration configuration)
{
//override services
services.AddScoped<IOrderProcessingService, CustomOrderProcessingService>();
}
/// <summary>
/// Configure the using of added middleware
/// </summary>
/// <param name="application">Builder for configuring an application's request pipeline</param>
public void Configure(IApplicationBuilder application)
{
}
/// <summary>
/// Gets order of this startup configuration implementation
/// </summary>
public int Order => 2001;
}
}
It should work for 4.6, 4.5 and 4.4 of nopCommerce version. Tested for 4.6

How to deserialize readonly fields using C# Driver for MongoDB?

I have a class which contains readonly private fields
public sealed class Company : AggregateRoot
{
private readonly List<TimeSeriesImportInfo> _executedImportInfo = new();
private readonly List<StockPrice> _stockPrices = new();
public Company(
Guid id,
string name,
string symbol)
: base(id)
{
Name = name;
Symbol = symbol;
}
public string Name { get; }
public string Symbol { get; }
public IReadOnlyCollection<TimeSeriesImportInfo> ExecutedImportInfo => _executedImportInfo.AsReadOnly();
public IReadOnlyCollection<StockPrice> StockPrices => _stockPrices.AsReadOnly();
public void AddExecutedImportInfo(DateTime month)
{
var timeSeriesImportInfo = new TimeSeriesImportInfo(month);
var hasAlreadyBeenHandled = ExecutedImportInfo.Contains(timeSeriesImportInfo);
if (hasAlreadyBeenHandled)
{
throw new ImportExecutedBeforeDomainException(Id, month);
}
_executedImportInfo.Add(timeSeriesImportInfo);
var importExecutedEvent = new ImportExecutedDomainEvent(Id, timeSeriesImportInfo.Month);
Enqueue(importExecutedEvent);
}
public void AddStockPrices(ICollection<StockPrice> stockPrices)
{
var hasAlreadyBeenImported = stockPrices.Any(newStockPrice =>
_stockPrices.Contains(newStockPrice));
if (hasAlreadyBeenImported)
{
throw new StockPriceAlreadyImportedDomainException(Id, stockPrices);
}
_stockPrices.AddRange(stockPrices);
var stockPricesAddedEvent = new StockPricesAddedDomainEvent(Id, stockPrices);
Enqueue(stockPricesAddedEvent);
}
}
I have configured the mapping for the company using the following method:
public static class Mapping
{
public static IServiceCollection AddMappings(this IServiceCollection services)
{
BsonClassMap.RegisterClassMap<Company>(classMap =>
{
classMap.AutoMap();
classMap.MapField("_executedImportInfo").SetElementName(nameof(Company.ExecutedImportInfo));
classMap.MapField("_stockPrices").SetElementName(nameof(Company.StockPrices));
});
return services;
}
}
The values of those private readonly fields are correctly saved to the database. However when im trying to fetch company object - those private fields can't be deserialized properly, because after the fetch they have default values (although they are persisted in the db). If i removed the "readonly" keyword from them - everything works correctly. I would like to avoid removing the readonly key and would like to not modify the Company class. How to achieve that in C#? Thanks for any help, regards.
it's not supported, readonly fields are skipped during deserialization after ctor called.

How do you transform asp netcore options before they are accessible

I've got options like this:
public class ApplicationSettings
{
public string Title { get; set; }
public string PluginFolders { get; set; }
}
and services like this:
public interface IWildcardResolver
{
string Resolve(string value);
}
public class WildcardResolver : IWildcardResolver
{
private readonly IHostingEnvironment _hostingEnvironment;
public WildcardResolver(IHostingEnvironment hostingEnvironment)
{
_hostingEnvironment = hostingEnvironment;
AddWildcard("%contentRootPath%", _hostingEnvironment.ContentRootPath);
AddWildcard("%webRootPath%", _hostingEnvironment.WebRootPath);
AddWildcard("%environment%", _hostingEnvironment.EnvironmentName);
}
private readonly Dictionary<string, string> _hardWiredValues = new Dictionary<string, string>();
/// <inheritdoc />
public string Resolve(string value)
{
var sb = new StringBuilder(value);
foreach (var pair in _hardWiredValues)
{
sb.Replace(pair.Key, pair.Value);
}
return sb.ToString();
}
public void AddWildcard(string name, string value)
{
if (_hardWiredValues.ContainsKey(name))
throw new Exception($"A value for the wildcard {name} already exists.");
_hardWiredValues.Add(name, value);
}
}
How can i make sure that before i access those settings through DI with IOptions<AppSettings> PluginFolders is translated (because it contains wildcards)? I've tried IConfigureOptions<AppSettings> and IPostConfigureOptions<AppSettings> but both of them appear to happen at a stage too late. it's like i am missing a IPreConfigureOptions or something.
public class PluginManager
{
private readonly IOptions<ApplicationSettings> _settings;
public PluginManager(IOptions<ApplicationSettings> settings)
{
_settings = settings;
// how do i get an instance here which makes sure that the ApplicationSettings.PluginPaths is already manipulated without doing it manually?
}
}
Doing it like this works, but then it feels like i am fighting the framework since i can't use IOptions<AppSettings> like everywhere else:
Ok. I found it out by digging through some microsoft component sources.
This is the solution:
public class ApplicationSettingsSetup : IConfigureOptions<ApplicationSettings>
{
private readonly IWildcardResolver _wildcardResolver;
public ApplicationSettingsSetup(IWildcardResolver wildcardResolver)
{
_wildcardResolver = wildcardResolver;
}
/// <inheritdoc />
public void Configure(ApplicationSettings options)
{
options.PluginFolders = _wildcardResolver.Resolve(options.PluginFolders);
}
}
Registration:
services.AddTransient<IConfigureOptions<ApplicationSettings>, ApplicationSettingsSetup>();
services.Configure<ApplicationSettings>(Configuration.GetSection("ApplicationSettings"));
Previously i was loading the AppSettings from the Configuration and registering IConfigurationOptions afterwards. Somehow i assumed the factory which creates Options would know to call IConfigureOptions first before returning the IOptions instance - this is wrong.
Changing the order fixed it.
This looks more along the lines of what you are trying to achieve
services.AddOptions<ApplicationSettings>()
.Configure<IWildcardResolver>((options, wildcardResolver) => {
options.PluginFolders = wildcardResolver.Resolve(options.PluginFolders);
//...
});
The above registers an action used to configure a particular type of options. Note: These are run before all .
Reference Use DI services to configure options

Roslyn Vsix stopped working after moving the project

I had a working Roslyn VSIX Project for analyzing resources that could be localizable. Everything was working fine until I moved the project to a new location.
The analyzer's still seem to run and trigger the Code fix, However, the Code Action is never Registered into visual studio, so there is no option for that fix for some reason.
My analyzer:
[DiagnosticAnalyzer(LanguageNames.CSharp)]
public class ConstDiagnosticAnalyzer : DiagnosticAnalyzer
{
public const string DiagnosticId = "Viventium.Localization.Tools.ConstantToResource";
// You can change these strings in the Resources.resx file. If you do not want your analyzer to be localize-able, you can use regular strings for Title and MessageFormat.
// See https://github.com/dotnet/roslyn/blob/master/docs/analyzers/Localizing%20Analyzers.md for more on localization
private static readonly LocalizableString Title = new LocalizableResourceString(nameof(Resources.ConstAnalyzerTitle), Resources.ResourceManager, typeof(Resources));
private static readonly LocalizableString MessageFormat = new LocalizableResourceString(nameof(Resources.ConstAnalyzerMessageFormat), Resources.ResourceManager, typeof(Resources));
private static readonly LocalizableString Description = new LocalizableResourceString(nameof(Resources.ConstAnalyzerDescription), Resources.ResourceManager, typeof(Resources));
private const string Category = "Naming";
private static ResourceLocalizationRule localizationRule = new ResourceLocalizationRule();
private static DiagnosticDescriptor Rule = new DiagnosticDescriptor(DiagnosticId, Title, MessageFormat, Category, DiagnosticSeverity.Warning, isEnabledByDefault: true, description: Description);
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics { get { return ImmutableArray.Create(Rule); } }
public override void Initialize(AnalysisContext context)
{
context.RegisterSyntaxNodeAction(AnalyzeConstDeclaration, SyntaxKind.FieldDeclaration);
}
public static void AnalyzeConstDeclaration(SyntaxNodeAnalysisContext context)
{
var fieldDeclaration = (FieldDeclarationSyntax)context.Node;
if (false == IsValidConstDeclaration(context, fieldDeclaration))
{
return;
}
var firstVariable = fieldDeclaration.Declaration.Variables.FirstOrDefault();
var firstSymbol = context.SemanticModel.GetDeclaredSymbol(firstVariable);
context.ReportDiagnostic(Diagnostic.Create(Rule, context.Node.GetLocation(), firstSymbol.Name));
}
private static bool VariableIsInResx(SyntaxNodeAnalysisContext context, FieldDeclarationSyntax fieldDeclaration)
{
var solution = context.GetSolution();
var documentPropertyMap = PropertyMapCache.GetDocumentPropertyMap(solution);
return localizationRule.IsFieldDeclaredInResx(fieldDeclaration, documentPropertyMap);
}
private static bool IsValidConstDeclaration(SyntaxNodeAnalysisContext context, FieldDeclarationSyntax fieldDeclaration)
{
if (false == fieldDeclaration.Modifiers.Any(SyntaxKind.ConstKeyword))
{
return false;
}
if (fieldDeclaration.Declaration.Variables.Count > 1)
{
return false;
}
var declaredVariable = fieldDeclaration.Declaration.Variables.FirstOrDefault();
var initializer = declaredVariable.Initializer;
if (initializer == null)
{
return false;
}
var constantValue = context.SemanticModel.GetConstantValue(initializer.Value);
if (!constantValue.HasValue)
{
return false;
}
var variableTypeName = fieldDeclaration.Declaration.Type;
var variableType = context.SemanticModel.GetTypeInfo(variableTypeName).ConvertedType;
if (variableType.SpecialType != SpecialType.System_String)
{
return false;
}
return true;
}
}
The Fix Provider:
[ExportCodeFixProvider(LanguageNames.CSharp, Name = nameof(ConstantToResourceCodeFixProvider)), Shared]
public class ConstantToResourceCodeFixProvider : CodeFixProvider
{
#region Consts
private const string title = "Convert Constant Use Resources";
#endregion
#region CodeFixProvider Overrides
public sealed override ImmutableArray<string> FixableDiagnosticIds
{
get { return ImmutableArray.Create(ConstDiagnosticAnalyzer.DiagnosticId); }
}
public sealed override FixAllProvider GetFixAllProvider()
{
// See https://github.com/dotnet/roslyn/blob/master/docs/analyzers/FixAllProvider.md for more information on Fix All Providers
return WellKnownFixAllProviders.BatchFixer;
}
public sealed override async Task RegisterCodeFixesAsync(CodeFixContext context)
{
var root = await context.Document.GetSyntaxRootAsync(context.CancellationToken).ConfigureAwait(false);
// TODO: Replace the following code with your own analysis, generating a CodeAction for each fix to suggest
var diagnostic = context.Diagnostics.First();
var diagnosticSpan = diagnostic.Location.SourceSpan;
// Find the type declaration identified by the diagnostic.
var declaration = root.FindToken(diagnosticSpan.Start).Parent.AncestorsAndSelf<FieldDeclarationSyntax>().First();
// Register a code action that will invoke the fix.
context.RegisterCodeFix(
CodeActionWithPreview.CreateStateful(
title: title,
createChangedDocument: (c, isPreview) => this.CheckConstForLocalization(context, declaration, isPreview, c),
equivalenceKey: title),
diagnostic);
}
#endregion
#region Analyzer Logic
private async Task<Document> CheckConstForLocalization(CodeFixContext context, FieldDeclarationSyntax field, bool isPreview, CancellationToken cancellationToken)
{
var documentPropertyMap = PropertyMapCache.GetDocumentPropertyMap(context.Document.Project.Solution);
var localizationRule = new ResourceLocalizationRule();
var updatedDocument = localizationRule.ConvertDocumentToUseResources(context.Document, documentPropertyMap, field, isPreview, cancellationToken);
return await updatedDocument;
}
#endregion
}
Can some one please give me a hint to of what is going on an how I can fix this?
After hours of debugging it turns out Diagnostic Analyzer's are not allowed to have periods in them...
public const string DiagnosticId = "Viventium_Localization_Tools_ConstantToResource";
The fix was quite simple I just changed the DiagnosticId to use underscores, and I've opened a bug with Roslyn

How can I deal with modules with different versions of the same dependencies in MEF?

At the moment, I have a module folder configured, and all my module assemblies and their dependencies live there. I worry that in six months time, someone builds a new module, and its dependencies overwrite the older versions of the dependencies.
Should I maybe develop some sort of module registry, where a developer registers a new module, and assigns it a sub-folder name in the modules folder? This kind of dampens the convenience of using a DirectoryCatalog though, if I have to tell the host about the modules.
I've had a similar problem in the past. Below I present my solution, which I think is similar to what you are trying to accomplish.
Using MEF like this is really fascinating, but here are my words of caution:
It gets complicated quick
You have to make a couple compromises like inheriting MarshalByRefObject and plugins not building with solution
And, as I decided, simpler is better! Other non-MEF designs may be a better choice.
Ok, disclaimers out of the way...
.NET allows you to load multiple versions of the same assembly into memory, but not to unload them. This is why my approach will require an AppDomain to allow you to unload modules when a new version becomes available.
The solution below allows you to copy plugin dlls into a 'plugins' folder in the bin directory at runtime. As new plugins are added and old ones are overwritten, the old will be unloaded and the new will be loaded without having to re-start your application. If you have multiple dlls with different versions in your directory at the same time, you may want to modify the PluginHost to read the assembly version through the file's properties and act accordingly.
There are three projects:
ConsoleApplication.dll (References Integration.dll only)
Integration.dll
TestPlugin.dll (References Integration.dll, must be copied to ConsoleApplication bin/Debug/plugins)
ConsoleApplication.dll
class Program
{
static void Main(string[] args)
{
var pluginHost = new PluginHost();
//Console.WriteLine("\r\nProgram:\r\n" + string.Join("\r\n", AppDomain.CurrentDomain.GetAssemblies().Select(a => a.GetName().Name)));
pluginHost.CallEach<ITestPlugin>(testPlugin => testPlugin.DoSomething());
//Console.ReadLine();
}
}
Integration.dll
PluginHost allows you to communicate with plugins. There should only ever be one instance of PluginHost. This also acts as a polling DirectoryCatalog.
public class PluginHost
{
public const string PluginRelativePath = #"plugins";
private static readonly object SyncRoot = new object();
private readonly string _pluginDirectory;
private const string PluginDomainName = "Plugins";
private readonly Dictionary<string, DateTime> _pluginModifiedDateDictionary = new Dictionary<string, DateTime>();
private PluginDomain _domain;
public PluginHost()
{
_pluginDirectory = AppDomain.CurrentDomain.BaseDirectory + PluginRelativePath;
CreatePluginDomain(PluginDomainName, _pluginDirectory);
Task.Factory.StartNew(() => CheckForPluginUpdatesForever(PluginDomainName, _pluginDirectory));
}
private void CreatePluginDomain(string pluginDomainName, string pluginDirectory)
{
_domain = new PluginDomain(pluginDomainName, pluginDirectory);
var files = GetPluginFiles(pluginDirectory);
_pluginModifiedDateDictionary.Clear();
foreach (var file in files)
{
_pluginModifiedDateDictionary[file] = File.GetLastWriteTime(file);
}
}
public void CallEach<T>(Action<T> call) where T : IPlugin
{
lock (SyncRoot)
{
var plugins = _domain.Resolve<IEnumerable<T>>();
if (plugins == null)
return;
foreach (var plugin in plugins)
{
call(plugin);
}
}
}
private void CheckForPluginUpdatesForever(string pluginDomainName, string pluginDirectory)
{
TryCheckForPluginUpdates(pluginDomainName, pluginDirectory);
Task.Delay(5000).ContinueWith(task => CheckForPluginUpdatesForever(pluginDomainName, pluginDirectory));
}
private void TryCheckForPluginUpdates(string pluginDomainName, string pluginDirectory)
{
try
{
CheckForPluginUpdates(pluginDomainName, pluginDirectory);
}
catch (Exception ex)
{
throw new Exception("Failed to check for plugin updates.", ex);
}
}
private void CheckForPluginUpdates(string pluginDomainName, string pluginDirectory)
{
var arePluginsUpdated = ArePluginsUpdated(pluginDirectory);
if (arePluginsUpdated)
RecreatePluginDomain(pluginDomainName, pluginDirectory);
}
private bool ArePluginsUpdated(string pluginDirectory)
{
var files = GetPluginFiles(pluginDirectory);
if (IsFileCountChanged(files))
return true;
return AreModifiedDatesChanged(files);
}
private static List<string> GetPluginFiles(string pluginDirectory)
{
if (!Directory.Exists(pluginDirectory))
return new List<string>();
return Directory.GetFiles(pluginDirectory, "*.dll").ToList();
}
private bool IsFileCountChanged(List<string> files)
{
return files.Count > _pluginModifiedDateDictionary.Count || files.Count < _pluginModifiedDateDictionary.Count;
}
private bool AreModifiedDatesChanged(List<string> files)
{
return files.Any(IsModifiedDateChanged);
}
private bool IsModifiedDateChanged(string file)
{
DateTime oldModifiedDate;
if (!_pluginModifiedDateDictionary.TryGetValue(file, out oldModifiedDate))
return true;
var newModifiedDate = File.GetLastWriteTime(file);
return oldModifiedDate != newModifiedDate;
}
private void RecreatePluginDomain(string pluginDomainName, string pluginDirectory)
{
lock (SyncRoot)
{
DestroyPluginDomain();
CreatePluginDomain(pluginDomainName, pluginDirectory);
}
}
private void DestroyPluginDomain()
{
if (_domain != null)
_domain.Dispose();
}
}
Autofac is a required dependency of this code. The PluginDomainDependencyResolver is instantiated in the plugin AppDomain.
[Serializable]
internal class PluginDomainDependencyResolver : MarshalByRefObject
{
private readonly IContainer _container;
private readonly List<string> _typesThatFailedToResolve = new List<string>();
public PluginDomainDependencyResolver()
{
_container = BuildContainer();
}
public T Resolve<T>() where T : class
{
var typeName = typeof(T).FullName;
var resolveWillFail = _typesThatFailedToResolve.Contains(typeName);
if (resolveWillFail)
return null;
var instance = ResolveIfExists<T>();
if (instance != null)
return instance;
_typesThatFailedToResolve.Add(typeName);
return null;
}
private T ResolveIfExists<T>() where T : class
{
T instance;
_container.TryResolve(out instance);
return instance;
}
private static IContainer BuildContainer()
{
var builder = new ContainerBuilder();
var assemblies = LoadAssemblies();
builder.RegisterAssemblyModules(assemblies); // Should we allow plugins to load dependencies in the Autofac container?
builder.RegisterAssemblyTypes(assemblies)
.Where(t => typeof(ITestPlugin).IsAssignableFrom(t))
.As<ITestPlugin>()
.SingleInstance();
return builder.Build();
}
private static Assembly[] LoadAssemblies()
{
var path = AppDomain.CurrentDomain.BaseDirectory + PluginHost.PluginRelativePath;
if (!Directory.Exists(path))
return new Assembly[]{};
var dlls = Directory.GetFiles(path, "*.dll").ToList();
dlls = GetAllDllsThatAreNotAlreadyLoaded(dlls);
var assemblies = dlls.Select(LoadAssembly).ToArray();
return assemblies;
}
private static List<string> GetAllDllsThatAreNotAlreadyLoaded(List<string> dlls)
{
var alreadyLoadedDllNames = GetAppDomainLoadedAssemblyNames();
return dlls.Where(dll => !IsAlreadyLoaded(alreadyLoadedDllNames, dll)).ToList();
}
private static List<string> GetAppDomainLoadedAssemblyNames()
{
var assemblies = AppDomain.CurrentDomain.GetAssemblies();
return assemblies.Select(a => a.GetName().Name).ToList();
}
private static bool IsAlreadyLoaded(List<string> alreadyLoadedDllNames, string file)
{
var fileInfo = new FileInfo(file);
var name = fileInfo.Name.Replace(fileInfo.Extension, string.Empty);
return alreadyLoadedDllNames.Any(dll => dll == name);
}
private static Assembly LoadAssembly(string path)
{
return Assembly.Load(File.ReadAllBytes(path));
}
}
This class represents the actual Plugin AppDomain. Assemblies resolved into this domain should load any dependencies they require from the bin/plugins folder first, followed by the bin folder, since it is part of the parent AppDomain.
internal class PluginDomain : IDisposable
{
private readonly string _name;
private readonly string _pluginDllPath;
private readonly AppDomain _domain;
private readonly PluginDomainDependencyResolver _container;
public PluginDomain(string name, string pluginDllPath)
{
_name = name;
_pluginDllPath = pluginDllPath;
_domain = CreateAppDomain();
_container = CreateInstance<PluginDomainDependencyResolver>();
}
public AppDomain CreateAppDomain()
{
var domaininfo = new AppDomainSetup
{
PrivateBinPath = _pluginDllPath
};
var evidence = AppDomain.CurrentDomain.Evidence;
return AppDomain.CreateDomain(_name, evidence, domaininfo);
}
private T CreateInstance<T>()
{
var assemblyName = typeof(T).Assembly.GetName().Name + ".dll";
var typeName = typeof(T).FullName;
if (typeName == null)
throw new Exception(string.Format("Type {0} had a null name.", typeof(T).FullName));
return (T)_domain.CreateInstanceFromAndUnwrap(assemblyName, typeName);
}
public T Resolve<T>() where T : class
{
return _container.Resolve<T>();
}
public void Dispose()
{
DestroyAppDomain();
}
private void DestroyAppDomain()
{
AppDomain.Unload(_domain);
}
}
Finally your plugin interfaces.
public interface IPlugin
{
// Marker Interface
}
The main application needs to know about each plugin so an interface is required. They must inherit IPlugin and be registered in the PluginHost BuildContainer method
public interface ITestPlugin : IPlugin
{
void DoSomething();
}
TestPlugin.dll
[Serializable]
public class TestPlugin : MarshalByRefObject, ITestPlugin
{
public void DoSomething()
{
//Console.WriteLine("\r\nTestPlugin:\r\n" + string.Join("\r\n", AppDomain.CurrentDomain.GetAssemblies().Select(a => a.GetName().Name)));
}
}
Final thoughts...
One reason that this solution worked for me is that my plugin instances from the AppDomain had a very short lifetime. However, I believe that modifications could be made to support plugin objects with a longer lifetime. This would likely require some compromises like a more advanced plugin wrapper that could perhaps recreate the object when the AppDomain is reloaded (see CallEach).

Categories

Resources