I am using nopCommerce version 3.5. I am trying to create a default Vendor account (attached with created customer) whenever any customer Register him/her self. I have created a Plugin that I have Installed from Admin section. I have checked in Log that Install/Uninstall & Register methods executes. But main method that I have written Is not fired at all. I tried after clearing the cache but no luck.
Following is the code I am using:
Folder Structure:
1) \Plugins\Nop.Plugin.Customer.Module\Controllers (Empty)
2) \Plugins\Nop.Plugin.Customer.Module\Data (Empty)
3) \Plugins\Nop.Plugin.Customer.Module\Domain (Empty)
4) \Plugins\Nop.Plugin.Customer.Module\Infrastructure
Files: 1) DependencyRegistrar.cs
5) \Plugins\Nop.Plugin.Customer.Module\Services
Files: 1) CustomerService.cs
6) \Plugins\Nop.Plugin.Customer.Module
Files: 1) CustomerModulePlugin.cs
Files: 2) Description.txt
Code in Files:
4) \Plugins\Nop.Plugin.Customer.Module\Infrastructure
Files: 1) DependencyRegistrar.cs
namespace Nop.Plugin.Tax.CountryStateZip.Infrastructure
{
public class DependencyRegistrar : IDependencyRegistrar
{
public virtual void Register(ContainerBuilder builder, ITypeFinder typeFinder)
{
builder.RegisterType<Nop.Plugin.Customer.Module.Services.CustomerService>().As<Nop.Services.Customers.ICustomerService>().InstancePerRequest();
////we cache presentation models between requests
//builder.RegisterType<CountryStateZipTaxProvider>()
// .WithParameter(ResolvedParameter.ForNamed<ICacheManager>("nop_cache_static"));
}
public int Order
{
get { return 1; }
}
}
}
5) \Plugins\Nop.Plugin.Customer.Module\Services
- Files: 1) CustomerService.cs
namespace Nop.Plugin.Customer.Module.Services
{
public class CustomerService : Nop.Services.Customers.CustomerService //Nop.Services.Customers.ICustomerService
{
#region Fields
private readonly IRepository<Nop.Core.Domain.Customers.Customer> _customerRepository;
private readonly IRepository<CustomerRole> _customerRoleRepository;
private readonly IRepository<GenericAttribute> _gaRepository;
private readonly IRepository<Order> _orderRepository;
private readonly IRepository<ForumPost> _forumPostRepository;
private readonly IRepository<ForumTopic> _forumTopicRepository;
private readonly IRepository<BlogComment> _blogCommentRepository;
private readonly IRepository<NewsComment> _newsCommentRepository;
private readonly IRepository<PollVotingRecord> _pollVotingRecordRepository;
private readonly IRepository<ProductReview> _productReviewRepository;
private readonly IRepository<ProductReviewHelpfulness> _productReviewHelpfulnessRepository;
private readonly IGenericAttributeService _genericAttributeService;
private readonly IDataProvider _dataProvider;
private readonly IDbContext _dbContext;
private readonly ICacheManager _cacheManager;
private readonly IEventPublisher _eventPublisher;
private readonly CustomerSettings _customerSettings;
private readonly CommonSettings _commonSettings;
private readonly Nop.Services.Vendors.IVendorService _vendorService;
private readonly Nop.Services.Customers.ICustomerService _customerService;
#endregion
public CustomerService(ICacheManager cacheManager,
IRepository<Nop.Core.Domain.Customers.Customer> customerRepository,
IRepository<CustomerRole> customerRoleRepository,
IRepository<GenericAttribute> gaRepository,
IRepository<Order> orderRepository,
IRepository<ForumPost> forumPostRepository,
IRepository<ForumTopic> forumTopicRepository,
IRepository<BlogComment> blogCommentRepository,
IRepository<NewsComment> newsCommentRepository,
IRepository<PollVotingRecord> pollVotingRecordRepository,
IRepository<ProductReview> productReviewRepository,
IRepository<ProductReviewHelpfulness> productReviewHelpfulnessRepository,
IGenericAttributeService genericAttributeService,
IDataProvider dataProvider,
IDbContext dbContext,
IEventPublisher eventPublisher,
CustomerSettings customerSettings,
CommonSettings commonSettings)
: base(
cacheManager,
customerRepository,
customerRoleRepository,
gaRepository,
orderRepository,
forumPostRepository,
forumTopicRepository,
blogCommentRepository,
newsCommentRepository,
pollVotingRecordRepository,
productReviewRepository,
productReviewHelpfulnessRepository,
genericAttributeService,
dataProvider,
dbContext,
eventPublisher,
customerSettings,
commonSettings
)
{
this._cacheManager = cacheManager;
this._customerRepository = customerRepository;
this._customerRoleRepository = customerRoleRepository;
this._gaRepository = gaRepository;
this._orderRepository = orderRepository;
this._forumPostRepository = forumPostRepository;
this._forumTopicRepository = forumTopicRepository;
this._blogCommentRepository = blogCommentRepository;
this._newsCommentRepository = newsCommentRepository;
this._pollVotingRecordRepository = pollVotingRecordRepository;
this._productReviewRepository = productReviewRepository;
this._productReviewHelpfulnessRepository = productReviewHelpfulnessRepository;
this._genericAttributeService = genericAttributeService;
this._dataProvider = dataProvider;
this._dbContext = dbContext;
this._eventPublisher = eventPublisher;
this._customerSettings = customerSettings;
this._commonSettings = commonSettings;
}
/// <summary>
/// Insert a customer
/// </summary>
/// <param name="customer">Customer</param>
public override void InsertCustomer(Nop.Core.Domain.Customers.Customer customer)
{
if (customer == null)
throw new ArgumentNullException("customer");
_customerRepository.Insert(customer);
//event notification
_eventPublisher.EntityInserted(customer);
// make registered user as a vendor too.
{
//Nop.Core.Domain.Vendors.Vendor _vendor = new Nop.Core.Domain.Vendors.Vendor();
var vendor = new Nop.Core.Domain.Vendors.Vendor()
{
Name = customer.Username,
Email = customer.Email,
Active = true
};
this._vendorService.InsertVendor(vendor);
customer.VendorId = vendor.Id;
var registeredRole = _customerService.GetCustomerRoleBySystemName(SystemCustomerRoleNames.Vendors);
if (registeredRole == null) throw new NopException("'Vendors' role could not be loaded");
customer.CustomerRoles.Add(registeredRole);
}
}
}
}
6) \Plugins\Nop.Plugin.Customer.Module
- Files: 1) CustomerModulePlugin.cs
namespace Nop.Plugin.Customer.Module
{
public class CustomerModulePlugin : BasePlugin, IConsumer<EntityInserted<Nop.Core.Domain.Customers.Customer>>
{
private readonly Nop.Services.Vendors.IVendorService _vendorService;
private readonly Nop.Services.Customers.ICustomerService _customerService;
public override void Install()
{
base.Install();
}
public void HandleEvent(EntityInserted<Nop.Core.Domain.Customers.Customer> customer)
{
if (customer == null)
throw new ArgumentNullException("customer");
// make registered user as a vendor too.
{
//Nop.Core.Domain.Vendors.Vendor _vendor = new Nop.Core.Domain.Vendors.Vendor();
var vendor = new Nop.Core.Domain.Vendors.Vendor()
{
Name = customer.Entity.Username,
Email = customer.Entity.Email,
Active = true
};
this._vendorService.InsertVendor(vendor);
customer.Entity.VendorId = vendor.Id;
var registeredRole = _customerService.GetCustomerRoleBySystemName(SystemCustomerRoleNames.Vendors);
if (registeredRole == null) throw new Exception("'Vendors' role could not be loaded");
customer.Entity.CustomerRoles.Add(registeredRole);
}
}
public override void Uninstall()
{
base.Uninstall();
}
}
}
This creates following folder:
1) \Presentation\Nop.Web\Plugins\Customer.Module
Files: 1) Nop.Plugin.Customer.Module.dll
Files: 2) Nop.Plugin.Customer.Module.pdb
Files: 3) Description.txt
Then I copied the generated files in Published code of nopCommerce.
Please let me know what I am doing wrong here?
Thanks
Found the issue,
Actually in DependencyRegistrar.cs file I wrongly typed following:
namespace Nop.Plugin.Tax.CountryStateZip.Infrastructure
Instead of:
namespace Nop.Plugin.Customer.Module.Infrastructure
Correcting fixed the issue.
Related
How is it possible to update the cache in the background to avoid cache misses?
In .net-core-2.1 I can add a memory cache like so:
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
services.AddMemoryCache();
}
}
Then it's very straightforward to use:
[Route("api")]
public class DataController : Controller
{
private readonly IMemoryCache _cache;
private readonly DataContext _dataContext;
public DataController(IMemoryCache cache, DataContext dataContext)
{
_cache = cache;
_dataContext = dataContext;
}
[HttpGet]
[Route("GimmeCachedData")]
public async Task<IActionResult> Get()
{
var cacheEntry = await
_cache.GetOrCreateAsync("MyCacheKey", entry =>
{
entry.AbsoluteExpiration = DateTime.Now.AddSeconds(20);
return Task.FromResult(_dataContext.GetOrders(DateTime.Now));
});
return Ok(cacheEntry);
}
}
However, after 20 seconds of amazingly fast cached powered bliss infused requests, as expected, the cached item is expired and the next request is stalled because of a cache-miss, and subsequent data loading.
Argh! so the cache only works sometimes. Why not have the option of having it work all the time?
How can I add functionality to:
return the old item (in the meantime) AND
automatically update the cache when items expire or are noticed to be expired so the next request will get the updated value?
In trying to solve this problem I have encountered 2 main obstacles with my implementation using an IHostedService:
When the cached item is expired it's evicted and no longer available; meaning I can't return it.
Updating cached items that require the database cause those calls to happen out of scope.
This cache update can kick off either directly after noticing a cache miss, or by actively monitoring for the next item to expire.
I've tried rolling my own cache (adding it as a singleton) using a ConcurrentDictionary<String, CacheItem>. The CacheItem class contains properties for the Value, Expiration, and a Factory (i.e.: a value-returning-delegate). But I found, as this delegate is probably set at request time and called in the IHostedService background thread, it caused context out of scope exception.
I have found a solution that seems to work.
Implement an IHostedService (extended from BackgroundService class). This class will serve as the background thread managed by the .net core framework. The background thread will keep the cache updates going (by calling ICache.UpdateCache as explained below), to avoid request-time cache misses.
public class CacheUpdateService : BackgroundService
{
private readonly ILogger<CacheUpdateService> _logger;
private readonly IServiceProvider _serviceProvider;
private readonly ICache _cache;
public CacheUpdateService(ILogger<CacheUpdateService> logger, IServiceProvider serviceProvider, ICache cache)
{
_logger = logger;
_serviceProvider = serviceProvider;
_cache = cache;
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
_logger.LogDebug("CacheUpdateService is starting.");
stoppingToken.Register(Dispose);
while (!stoppingToken.IsCancellationRequested)
{
try
{
using (var scope = _serviceProvider.CreateScope())
{
var dataContext = scope.ServiceProvider.GetRequiredService<DataContext>();
// This tight loop calls the UpdateCache, which will block if no updates are necessary
await Task.Run(() => _cache.UpdateCache(dataContext), stoppingToken);
}
}
catch (Exception ex)
{
_logger.LogError(ex, "Exception in the CacheUpdateService");
}
}
_logger.LogDebug("CacheUpdateService has stopped.");
}
public override void Dispose()
{
using(var scope = _serviceProvider.CreateScope())
{
var scopedProcessingService = scope.ServiceProvider.GetRequiredService<ICache>();
// Dispose here on ICache will release any blocks
scopedProcessingService.Dispose();
}
base.Dispose();
}
}
The Cache class below implements the background UpdateCache method which will update 1 expired item at a time. Prioritizing the one most expired. It also implements request-scoped GetOrCreate method. Note I'm using a delegate (Func<IDataContext, Object>) in the CacheEntry as the value population factory. This allows Cache class to inject a properly scoped DataContext (received from the IHostedService) and it also allows the caller to specify which method of the DataContext is called to get the results of the specific cache key value. Notice I'm using an AutoResetEvent to wait for 1st-time data population as well as a timer to kick off the next cache refresh. This implementation will suffer a cache-miss for the 1st time the item is called (and I guess after it hasn't been called for more than 1 hour; as it will be evicted after 1 hr.).
public class CacheEntry
{
public String Key { get; set; }
public Object Value { get; set; }
public Boolean Updating { get; set; }
public Int32 ExpirySeconds { get; set; }
public DateTime Expiration { get; set; }
public DateTime LastAccessed { get; set; }
public Func<IDataContext, Object> ValueFactory { get; set; }
}
public interface ICache : IDisposable
{
void UpdateCache(IDataContext dataContext);
T GetOrCreate<T>(String key, Func<IDataContext, T> factory, Int32 expirySeconds = 0) where T : class;
}
public class Cache : ICache
{
private readonly ILogger _logger;
private readonly ConcurrentDictionary<String, CacheEntry> _cache;
private readonly AutoResetEvent _governor;
public Cache(ILogger<Cache> logger)
{
_logger = logger;
_cache = new ConcurrentDictionary<String, CacheEntry>();
_governor = new AutoResetEvent(false);
}
public void Dispose()
{
_governor.Set();
}
public static Int32 CacheForHour => 3600;
public static Int32 CacheForDay => 86400;
public static Int32 CacheIndefinitely => 0;
public void UpdateCache(IDataContext dataContext)
{
var evictees = _cache.Values
.Where(entry => entry.LastAccessed.AddHours(1) < DateTime.Now)
.Select(entry => entry.Key)
.ToList();
foreach (var evictee in evictees)
{
_logger.LogDebug($"Evicting: {evictee}...");
_cache.Remove(evictee, out _);
}
var earliest = _cache.Values
.Where(entry => !entry.Updating)
.OrderBy(entry => entry.Expiration)
.FirstOrDefault();
if (earliest == null || earliest.Expiration > DateTime.Now)
{
var timeout = (Int32) (earliest?.Expiration.Subtract(DateTime.Now).TotalMilliseconds ?? -1);
_logger.LogDebug($"Waiting {timeout}ms for next expiry...");
_governor.WaitOne(timeout);
return;
}
try
{
_logger.LogDebug($"Updating cache for: {earliest.Key}...");
earliest.Updating = true;
earliest.Value = earliest.ValueFactory(dataContext);
earliest.Expiration = earliest.ExpirySeconds > 0
? DateTime.Now.AddSeconds(earliest.ExpirySeconds)
: DateTime.MaxValue;
_governor.Set();
}
finally
{
earliest.Updating = false;
}
}
public T GetOrCreate<T>(String key, Func<IDataContext, T> factory, Int32 expirySeconds = -1) where T : class
{
var success = _cache.TryGetValue(key, out var entry);
if (success && entry.Value != null)
{
entry.LastAccessed = DateTime.Now;
return (T) entry.Value;
}
if (entry == null)
{
_logger.LogDebug($"Adding new entry to the cache: {key}...");
entry = new CacheEntry
{
Key = key,
Expiration = DateTime.MinValue,
ExpirySeconds = expirySeconds,
LastAccessed = DateTime.Now,
ValueFactory = factory
};
_cache.TryAdd(key, entry);
_governor.Set();
}
while (entry.Value == null)
{
_logger.LogDebug($"Waiting for 1st time cache update: {entry.Key}...");
_governor.WaitOne();
}
return (T)entry.Value;
}
}
The DataContext class is then created like so. Using Dapper for example to retrieve the data from the database:
public class DataContext : DbContext, IDataContext
{
private readonly IOptions<Settings> _settings;
private String _databaseServer;
public DataContext(IOptions<Settings> settings)
{
_settings = settings;
}
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
base.OnConfiguring(optionsBuilder);
optionsBuilder.UseSqlServer(_settings.Value.ConnectionString);
}
public IEnumerable<OrderInfo> GetOrders(DateTime date)
{
return Database.GetDbConnection().Query<OrderInfo>(
$"SchemaName.usp_GetOrders",
new {Date = date},
commandType: CommandType.StoredProcedure);
}
}
In the controllers the ICache is injected and used as follows:
[HttpGet]
[Route("Orders/{date}")]
public IActionResult GetOrders(DateTime date)
{
var result = _cache.GetOrCreate(
$"GetOrders_{date:yyyyMMdd}",
context => context.GetOrders(date),
date.Date < DateTime.Today ? Cache.CacheIndefinitely : 20);
return Ok(result);
}
Finally register the classes as a Singleton in the DI setup
services.AddOptions();
services.Configure<Settings>(Configuration);
services.AddLogging();
services.AddDbContext<DataContext>();
services.AddSingleton<ICache, Cache>();
services.AddSingleton<IHostedService, CacheUpdateService>();
I created a similar IHostedService based project https://github.com/dpatekar/CacheAnt
Take a look, it is very simple to use.
It is available as a NuGet package also.
I am trying to launch a interface method and bind it to a Xamarin list view but I am having some trouble. My interface is below
readonly string url = "http://myinternaliis/api/";
readonly IHttpService httpService;
public ApiClient(IHttpService httpService)
{
this.httpService = httpService;
}
public Task<List<JobsList>> GetJobs() => httpService.Get<List<JobsList>>($"{url}job");
I am trying to bind it to my list view as such please correct me if this is wrong. Should I be creating a collection of some description
public partial class JobsPage : ContentPage
{
readonly string url = "http://myinternaliis/api/";
public IHttpService httpService;
public IApi FuleApiClient;
public JobsPage ()
{
InitializeComponent ();
FuelApiClient _client = new FuelApiClient(httpService);
this.JobListing.ItemsSource = _client.GetJobs();
}
You need to await your task.
public partial class JobsPage : ContentPage
{
readonly string url = "http://myinternaliis/api/";
public IHttpService httpService;
public IApi FuleApiClient;
public JobsPage ()
{
InitializeComponent ();
FuelApiClient _client = new FuelApiClient(httpService);
SetItemSource();
}
private Task SetItemSource()
. {
. JobListing.ItemsSource = await _client.GetJobs();
}
}
I am currently working on an Enterprise Architect addin, and i really need to use some custom hotkeys for user preference. It is working correctly by using functionality from this article: https://community.sparxsystems.com/community-resources/805-triggering-add-in-functionality-with-custom-hotkeys-in-enterprise-architect , but with this way of using custom hotkeys Enterprise Architect is constantly using one core of the processor massively -> if i remove the addin dll, and delete the key from the registry this problem disappears, and i am sure now that the custom hotkey functionality causes it.
Can anyone help me by offering another way of using custom hotkeys in an enterprise architect addin? Searching for another solution for a long time now, but haven't achieved any progress in it yet.
Tamas
EDIT... FOUND THE SOLUTION: modify InvisibleHotKeyForm.cs and Hotkey.cs
InvisibleHotKeyForm.cs modification:
delete "worker_DoWork" method and
public partial class InvisibleHotKeyForm : Form
{
private const double Button_MilliSec = 10.0;
private readonly IEnumerable<Hotkey> _hotkeys;
private readonly int _thisProcessId;
private int _lastActiveProcessId;
private readonly BackgroundWorker _worker;
private System.Timers.Timer Timer_Button;
public InvisibleHotKeyForm(IEnumerable<Hotkey> hotkeys)
{
InitializeComponent();
this._thisProcessId = Process.GetCurrentProcess().Id;
this._worker = new BackgroundWorker();
this.Timer_Init(10.0);
this._hotkeys = hotkeys;
this._lastActiveProcessId = this._thisProcessId;
Closing += (sender, eventArgs) => ((List<Hotkey>)_hotkeys).ForEach(key => key.Dispose());
}
private void Timer_Init(double Millisec)
{
this.Timer_Button = new System.Timers.Timer();
this.Timer_Button.Elapsed += new ElapsedEventHandler(OnTimedEvent);
this.Timer_Button.Interval = Millisec;
this.Timer_Button.Enabled = true;
}
private void OnTimedEvent(object sender, ElapsedEventArgs e)
{
int id = ActiveProcess.GetActiveProcess().Id;
if (this._lastActiveProcessId == id)
return;
if (this._thisProcessId == id)
{
this.BeginInvoke((Delegate)new MethodInvoker(RegisterHotKeys));
}
else if (this._thisProcessId != id)
{
this.BeginInvoke((Delegate)new MethodInvoker(UnregisterHotKeys));
}
this._lastActiveProcessId = id;
}
.
.
.
and Hotkey.cs modification:
public class Hotkey : IDisposable
{
public const int WM_HOTKEY_MSG_ID = 786;
private Keys Key { get; set; }
private Modifiers Modifiers { get; set; }
public HotkeyHandler Handler { get; private set; }
private int Id { get; set; }
private IWin32Window _registeredWindow;
private bool _registered;
public Hotkey(Keys key, Modifiers modifiers, HotkeyHandler handler)
{
this._registeredWindow = (IWin32Window)null;
this._registered = false;
this.Key = key;
this.Modifiers = modifiers;
this.Handler = handler;
Id = GetHashCode();
}
.
.
.
PRISM 6.2 / EntityFramework 6.3.1 / StockTrader UI / UnityContainer
I acutally having a project with PRISM 5.0.0 and want to update to PRISM 6.2. The project runs fine on 5.0.0, but when I'm updating to 6.2 I got the following problem with the InteractionRequest.
When I navigate to a view/viewmodel with Notifications for the first time, everything works and I can handle the InteractionRequests as usual. If I navigate back and navigate to the view again with a new object, the InteractionsRequest raised the notification twice. (...navigating back and go to again -> raised three times and so on).
In some reasons, the message "This Visual is not connected to a PresentationSource" will occur.
I figure out, that the _invocationCount and _invocationList on the InteractionRequest will not be set to "0"/"null" with PRISM 6.2. So, i think the InteractionRequest will call the notification more than one time. Attached, are screenshots from PRISM 5 and PRISM 6.2.
How can I handle this and solve the problem. In my opinion, it's not a big thing but I actually spent a lot of time to find a solution. Thanks...
PRISM 5.0.0 - working fine
PRISM 6.2 - issue
2017.02.22 Added Sourccode. Software is used to handle devices in datacenters. I deleted all unnecessary sourcecode, but with these files the problem still occur. Perhaps this is a try to find my issue....
Rackmodule.cs
-> Initialize Module Rack
public class RackModule : IModule
{
private readonly IRegionManager _regionManager;
private readonly IUnityContainer _container;
public RackModule(IRegionManager regionManager, IUnityContainer container)
{
_regionManager = regionManager;
_container = container;
}
public void Initialize()
{
_container.RegisterType<IRackViewModel, RackViewModel>(new ContainerControlledLifetimeManager());
_container.RegisterType<IRackToolbarViewModel, RackToolbarViewModel>(new ContainerControlledLifetimeManager());
_container.RegisterType<IRackStatusbarViewModel, RackStatusbarViewModel>(new ContainerControlledLifetimeManager());
_container.RegisterType<IRackSummaryViewModel, RackSummaryViewModel>(new ContainerControlledLifetimeManager());
_container.RegisterType<IGeneralDataViewModel, GeneralDataViewModel>(new ContainerControlledLifetimeManager());
_container.RegisterType<IPlanDataViewModel, PlanDataViewModel>(new ContainerControlledLifetimeManager());
_container.RegisterType<IRackDataViewModel, RackDataViewModel>(new ContainerControlledLifetimeManager());
_container.RegisterType<Object, GeneralDataView>(typeof(GeneralDataView).FullName);
IRegion region = this._regionManager.Regions["MainRegion"];
var rackView = _container.Resolve<RackView>();
region.Add(rackView, "RackView");
region.Activate(rackView);
IRegion toolbarregion = this._regionManager.Regions["RackToolbarRegion"];
var toolbarView = _container.Resolve<RackToolbarView>();
toolbarregion.Add(toolbarView, "RackToolbarView");
toolbarregion.Activate(toolbarView);
IRegion statusbarregion = this._regionManager.Regions["RackStatusbarRegion"];
var statusbarView = _container.Resolve<RackStatusbarView>();
statusbarregion.Add(statusbarView, "RackStatusbarView");
statusbarregion.Activate(statusbarView);
_container.RegisterType<Object, RackSummaryView>(typeof(RackSummaryView).FullName);
_regionManager.RequestNavigate(RegionNames.RackContentRegion, typeof(RackSummaryView).FullName);
}
}
RackSummaryViewModel.cs
-> Overview of racks. Go to RackDataView, when click on object
public class RackSummaryViewModel : BindableBase, IRackSummaryViewModel
{
private readonly IRegionManager _regionManager;
private readonly IEventAggregator _eventAggregator;
private readonly IUnityContainer _container;
public DelegateCommand<SearchEventArgs> OnSearch { get; private set; }
public DelegateCommand AdvancedRackSearchCommand { get; private set; }
public InteractionRequest<AdvancedRackSearchNotification> AdvancedSearchRequest { get; private set; }
private ObservableCollection<RackSummaryEntry> _racks;
public ObservableCollection<RackSummaryEntry> Racks
{
get { return _racks; }
private set {SetProperty(ref _racks, value);}
}
private RackSummaryEntry _currentRack;
public RackSummaryEntry CurrentRack
{
get { return _currentRack; }
set
{
if (SetProperty(ref _currentRack, value))
{
if (_currentRack != null)
{
var parameters = new NavigationParameters();
parameters.Add("RackID", _currentRack.PrimaryKey.ToString(GuidNumericFormatSpecifier));
_container.RegisterType<Object, RackDataView>(typeof(RackDataView).FullName);
_regionManager.RequestNavigate(RegionNames.RackContentRegion, new Uri(typeof(RackDataView).FullName + parameters, UriKind.Relative));
}
}
}
}
private const string GuidNumericFormatSpecifier = "N";
public RackSummaryViewModel(IEventAggregator eventAggregator, IRegionManager regionManager, IUnityContainer container)
{
_regionManager = regionManager;
_eventAggregator = eventAggregator;
_container = container;
ISessionFactory factory = new SessionFactory();
container.RegisterType<IRepository, Repository>(new InjectionConstructor(factory.CurrentUoW));
IUnitOfWork unitOfWork = factory.CurrentUoW;
IRepository localrepository = new Repository(unitOfWork);
var query = localrepository.GetList<DMS.Domain.Domain.Rack>();
Racks = new ObservableCollection<RackSummaryEntry>(query
.Select(x => new RackSummaryEntry
{
PrimaryKey = x.PrimaryKey,
Country = x.Location.Address.Country,
City = x.Location.Address.City,
Street = x.Location.Address.Street,
Building = x.Location.BuildingName,
RoomName = x.Location.RoomName,
RackName = x.RackName,
RackHeight = x.RackHeight
}).ToList());
}
}
RackDataViewModel.cs
-> Only Button "Save" und "Go Back"
public class RackDataViewModel : BindableBase, IRackDataViewModel, INavigationAware, IRegionMemberLifetime
{
private IRegionNavigationJournal _navigationJournal;
private readonly IRegionManager _regionManager;
private readonly IUnityContainer _container;
private readonly IEventAggregator _eventAggregator;
public DelegateCommand GoBackCommand { get; private set; }
public DelegateCommand SaveCommand { get; private set; }
public InteractionRequest<INotification> SaveNotificationRequest { get; private set; }
private const string RackIdKey = "RackID";
private const string EType = "EditType";
private const string GuidNumericFormatSpecifier = "N";
public DMS.Domain.Domain.Rack rack;
// [InjectionConstructor] check if necessary
public RackDataViewModel(IRegionManager regionManager, IRegionNavigationJournal navigationJournal, IUnityContainer container, IEventAggregator eventAggregator, ILoggerFactory logFactory)
{
_regionManager = regionManager;
_navigationJournal = navigationJournal;
_container = container;
_eventAggregator = eventAggregator;
GoBackCommand = new DelegateCommand(OnGoBackExecute);
SaveCommand = new DelegateCommand(OnSaveExecute);
SaveNotificationRequest = new InteractionRequest<INotification>();
}
private void OnGoBackExecute()
{
if (_navigationJournal != null)
{
while (_navigationJournal.CanGoBack)
_navigationJournal.GoBack();
_regionManager.Regions.Remove(RegionNames.RackGeneralDataRegion);
}
}
private void OnSaveExecute()
{
SaveNotificationRequest.Raise(new Notification { Content = "Save changes submitted", Title = "Save changes" });
}
public bool KeepAlive
{
get { return false; }
}
private Guid? GetRequestedRackId(NavigationContext navigationContext)
{
var rack = navigationContext.Parameters[RackIdKey];
Guid rackId;
if (rack != null)
{
if (rack is Guid)
rackId = (Guid)rack;
else
rackId = Guid.Parse(rack.ToString());
return rackId;
}
return null;
}
bool INavigationAware.IsNavigationTarget(NavigationContext navigationContext)
{
var type = navigationContext.Parameters[EType];
if (rack == null || ((string)type) == "New")
return true;
var requestedRackId = GetRequestedRackId(navigationContext);
return requestedRackId.HasValue && requestedRackId.Value == rack.PrimaryKey;
}
void INavigationAware.OnNavigatedFrom(NavigationContext navigationContext)
{
}
void INavigationAware.OnNavigatedTo(NavigationContext navigationContext)
{
Guid? rackId;
NavigationParameters parameters = new NavigationParameters();
string key = navigationContext.Parameters[RackIdKey].ToString();
rackId = GetRequestedRackId(navigationContext);
parameters = navigationContext.Parameters;
_regionManager.RequestNavigate(RegionNames.RackGeneralDataRegion, new Uri(typeof(GeneralDataView).FullName + parameters, UriKind.Relative));
_navigationJournal = navigationContext.NavigationService.Journal;
}
}
GeneralDataViewModel.cs
-> Is in region of RackDataView with the data of the racks
public class GeneralDataViewModel : BindableBase, IGeneralDataViewModel, INavigationAware
{
private IRegionNavigationJournal _navigationJournal;
private readonly IRegionManager _regionManager;
private readonly IRepository _repository;
private const string RackIdKey = "RackID";
public DMS.Domain.Domain.Rack Rack { get; set; }
public List<Location> Locations { get; set; }
public GeneralDataViewModel(IRegionManager regionManager, IRepository repository)
{
_regionManager = regionManager;
_repository = repository;
}
private Guid? GetRequestedRackId(NavigationContext navigationContext)
{
var rack = navigationContext.Parameters[RackIdKey];
Guid rackId;
if (rack != null)
{
if (rack is Guid)
rackId = (Guid)rack;
else
rackId = Guid.Parse(rack.ToString());
return rackId;
}
return null;
}
bool INavigationAware.IsNavigationTarget(NavigationContext navigationContext)
{
if (Rack == null)
return true;
var requestedRackId = GetRequestedRackId(navigationContext);
return requestedRackId.HasValue && requestedRackId.Value == Rack.PrimaryKey;
}
void INavigationAware.OnNavigatedFrom(NavigationContext navigationContext)
{
// Intentionally not implemented.
}
void INavigationAware.OnNavigatedTo(NavigationContext navigationContext)
{
var rackId = GetRequestedRackId(navigationContext);
Rack = _repository.GetEntity<DMS.Domain.Domain.Rack>(rackId);
_navigationJournal = navigationContext.NavigationService.Journal;
}
}
I am pretty new to Wicket and i have some difficulties with using resource references. I am using wicket 1.5.4 and have following problem: I store images on the file system. I have class ImageElement which holds part of the file path relative to configured rootFilePath (i.e dir1/dir2/img1.png). On the page I add Image as follows:
new Image("id",ImagesResourceReference.get(), pageParameters)
where page parameters includes image path parameter (path="/dir1/dir2/img1.png"). My questions are:
Is it the simplest way of serving images from the file system?
Is it ok to use ResourceReference with static method? or I should construct each time new ResourceReference? I saw that in previous version it was possible to use new ResourceReference(globalId), but it seems not to be the case anymore. If so what is the global resource reference for? So far as I understand resource reference is supposed to be factory for resources so it would be rather strange to create new factory for each resource request.
The last question is, how can i pass the path to the image in a better way so that i do not have to concatenate indexed parameters to build the path once respond method is invoked on ImageResource.
What would be the best scenario to get it working in efficient and simple way, i saw the example in 'Wicket in action', but this is meant for dynamic image generation from db and am not sure if it suites for my case
My implementation of ResourceReference which I mounted in Application under "/images" path, looks as follows:
public class ImagesResourceReference extends ResourceReference {
private static String rootFileDirectory;
private static ImagesResourceReference instance;
private ImagesResourceReference() {
super(ImagesResourceReference.class, "imagesResourcesReference");
}
public static ImagesResourceReference get() {
if(instance == null) {
if(StringUtils.isNotBlank(rootFileDirectory)) {
instance = new ImagesResourceReference();
} else {
throw new IllegalStateException("Parameter configuring root directory " +
"where images are saved is not set");
}
}
return instance;
}
public static void setRootFileDirectory(String rootFileDirectory) {
ImagesResourceReference.rootFileDirectory = rootFileDirectory;
}
private static final long serialVersionUID = 1L;
#Override
public IResource getResource() {
return new ImageResource(rootFileDirectory);
}
private static class ImageResource implements IResource {
private static final long serialVersionUID = 1L;
private final String rootFileDirectory;
public ImageResource(String rootFileDirectory) {
this.rootFileDirectory = rootFileDirectory;
}
#Override
public void respond(Attributes attributes) {
PageParameters parameters = attributes.getParameters();
List<String> indexedParams = getAllIndexedParameters(parameters);
if(!indexedParams.isEmpty() && isValidImagePath(indexedParams)) {
String pathToRequestedImage = getImagePath(indexedParams);
FileResourceStream fileResourceStream = new FileResourceStream(new File(pathToRequestedImage));
ResourceStreamResource resource = new ResourceStreamResource(fileResourceStream);
resource.respond(attributes);
}
}
private boolean isValidImagePath(List<String> indexedParams) {
String fileName = indexedParams.get(indexedParams.size() -1);
return !FilenameUtils.getExtension(fileName).isEmpty();
}
private List<String> getAllIndexedParameters(PageParameters parameters) {
int indexedparamCount = parameters.getIndexedCount();
List<String> indexedParameters = new ArrayList<String>();
for(int i=0; i<indexedparamCount ;i++) {
indexedParameters.add(parameters.get(i).toString());
}
return indexedParameters;
}
private String getImagePath(List<String> indexedParams) {
return rootFileDirectory + File.separator + StringUtils.join(indexedParams, File.separator);
}
}
Any help and advices appreciated! Thanks in advance.
You could use it as a shared resource:
public class WicketApplication extends WebApplication {
#Override
public Class<HomePage> getHomePage() {
return HomePage.class;
}
#Override
public void init() {
super.init();
getSharedResources().add("downloads", new FolderContentResource(new File("C:\\Users\\ronald.tetsuo\\Downloads")));
mountResource("downloads", new SharedResourceReference("downloads"));
}
static class FolderContentResource implements IResource {
private final File rootFolder;
public FolderContentResource(File rootFolder) {
this.rootFolder = rootFolder;
}
public void respond(Attributes attributes) {
PageParameters parameters = attributes.getParameters();
String fileName = parameters.get(0).toString();
File file = new File(rootFolder, fileName);
FileResourceStream fileResourceStream = new FileResourceStream(file);
ResourceStreamResource resource = new ResourceStreamResource(fileResourceStream);
resource.respond(attributes);
}
}
}
You can still use ResourceReferences with global IDs. You just have to use a SharedResourceReference. This is probably better, too.
add(new Image("image", new SharedResourceReference("mySharedResourceRef", parameters));
I would try to avoid building paths from URL parameters. This can easily end up in security leaks.