Change ViewModel Property value using plugin in nopCommerce - plugins

I am working on nopCommerce v3.90. I have a requirement to a plugin which would update the original Price of the Product by some percentage ratio based on the settings performed within plugin settings section, without altering the existing nopCommerce model structure.
Hence, each time the product's price is displayed, one should be able to see the new updated price (based on the action performed in a plugin) instead of the price from the database.
Can anyone help me with this?
Existing Model class in nopCommerce
public partial class ProductPriceModel : BaseNopModel
{
//block of statements
public string Price { get; set; } //the property whose value need to be changed from plugin
//block of statements
}

In 3.9 the options I know are
Override PrepareProductPriceModel method in IProductModelFactory class and provide your custom implementation using dependency override
Implement an ActionFilter to customize the ProductPriceModel before it gets used in the view.
In 4.0 this is very easy. You only have to subscribe to ModelPreparedEvent and then customize the ProductPriceModel.
Override IProductModelFactory
public class CustomProductModelFactory : ProductModelFactory
{
// ctor ....
protected override ProductDetailsModel.ProductPriceModel PrepareProductPriceModel(Product product)
{
// your own logic to prepare price model
}
}
In your plugin dependency registrar
builder.RegisterType<CustomProductModelFactory>()
.As<IProductModelFactory>()
.WithParameter(ResolvedParameter.ForNamed<ICacheManager>("nop_cache_static"))
.InstancePerLifetimeScope();
Implement ActionFilter
public class PriceInterceptor : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
if (filterContext == null) throw new ArgumentNullException(nameof(filterContext));
if (filterContext.HttpContext?.Request == null) return;
if (filterContext.Controller is Controller controller)
{
if (controller.ViewData.Model is ProductDetailsModel model)
{
// your own logic to prepare price model
}
}
}
}
And to dynamically provide your ActionFilter
public class PriceInterceptorFilterProvider : IFilterProvider
{
public IEnumerable<Filter> GetFilters(ControllerContext controllerContext, ActionDescriptor actionDescriptor)
{
return new[] { new Filter(new PriceInterceptor(), FilterScope.Action, null) };
}
}
In your plugin dependency registrar
builder.RegisterType<PriceInterceptorFilterProvider>()
.As<IFilterProvider>();
Subscribe to ModelPreparedEvent<ProductDetailsModel> (nopCommerce 4.0)
public class PriceInterceptor : IConsumer<ModelPreparedEvent<ProductDetailsModel>>
{
public void HandleEvent(ModelPreparedEvent<ProductDetailsModel> eventMessage)
{
// your own logic to prepare price model
}
}

Related

Passing parent component to all child components in blazor via rendertree

I created an custom form component in blazor and inherited from the default EditForm component to add some functionality.
public class CustomForm : EditForm
I want to pass the instance of the form component to all its children, so a child can retrieve it via a cascading parameter like so
[CascadingParameter]
public CustomForm Form { get; set; }
I took over the BuildRenderTree Method of the default Editform
protected override void BuildRenderTree(RenderTreeBuilder builder)
{
Debug.Assert(EditContext != null);
// If _editContext changes, tear down and recreate all descendants.
// This is so we can safely use the IsFixed optimization on CascadingValue,
// optimizing for the common case where _editContext never changes.
builder.OpenRegion(EditContext.GetHashCode());
builder.OpenElement(0, "form");
builder.AddMultipleAttributes(1, AdditionalAttributes);
builder.AddAttribute(2, "onsubmit", _handleSubmitDelegate);
builder.OpenComponent<CascadingValue<EditContext>>(3);
builder.AddAttribute(4, "IsFixed", true);
builder.AddAttribute(5, "Value", EditContext);
builder.AddAttribute(6, "ChildContent", ChildContent?.Invoke(EditContext));
builder.CloseComponent();
builder.CloseElement();
builder.CloseRegion();
}
But i do not know how to manipulate this code to achieve my goal.
It's possible to pass any object as cascading value so you can override the BuildRenderTree like this and the CustomForm instance will be passed to the child components. Explicit cast to RenderFragment is needed to create delegate of this type, otherwise CascadingValue<>.SetParametersAsync will throw an invalid cast exception.
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Forms;
using Microsoft.AspNetCore.Components.Rendering;
namespace BlazorTest
{
public class CustomForm : EditForm
{
protected override void BuildRenderTree(RenderTreeBuilder builder)
{
builder.OpenRegion(0);
builder.OpenComponent<CascadingValue<CustomForm>>(1);
builder.AddAttribute(2, "Value", this);
builder.AddAttribute(3, "ChildContent", (RenderFragment)base.BuildRenderTree);
builder.CloseComponent();
builder.CloseRegion();
}
}
}
I tested this with these components:
CustomFormComponent.razor
<h3>CustomFormComponent</h3>
#code {
private CustomForm _form;
[CascadingParameter]
public CustomForm Form
{
get => _form;
set
{
_form = value;
Console.WriteLine("CustomForm set in CustomFormInput");
}
}
}
TestPage.razor
#page "/test"
<CustomForm Model=model>
<CustomFormComponent />
</CustomForm>
#code {
private AModel model = new();
private class AModel
{
public string Name { get; set; }
}
}
I just want to add that I'm not an expert in this and I'm not sure if you should do it this way. Maybe it would be better to use EditForm as a child component of the CustomForm because you don't have to worry about BuildRenderTree method when using razor markup.
<CascadingValue Value=this>
<EditForm Model=model>
#ChildContent
</EditForm
</CascadingValue>

Using Autofac for WebForms and n-tier architecture

I am introducing unit testing to an existing webforms application. I am using Moq and Autofac. I'm trying to keep code changes to a minimum, but need to implement the unit testing.
So I have a presentation assembly, a business layer, and a data access layer. My business layer looks something like this:
public class EmployeeBL
{
public Employee SelectById(int id)
{
return new EmployeeDA().SelectById(id);
}
}
My Data access looks something like this:
public class EmployeeDA
{
// unitOfWork defined in the constructor
public Employee SelectById(int id)
{
return unitOfWork.Employees.Where(e => e.id == id);
}
}
Autofac's webforms assembly allows you to declare a public property and specify web.config settings to automatically inject a dependency via HttpModule. To test the EmployeeBL, I made the changes to web.config and declared EmployeeDA as a property:
// Modified for testing
[InjectProperties]
public class EmployeeBL
{
// public property to allow Autofac property injection
public IEmployeeDA EmployeeDA { get; set; }
public Employee SelectById(int id)
{
return EmployeeDA.SelectById(id);
}
}
This works for running the webforms application, but the problem is I need to unit test the business layer independently of a webforms instance. I would like to keep the [InjectProperties] attribute on my EmployeeBL, but I can't tell by the documentation how I can get the EmployeeDA to have its properties injected.
public class EmployeeBLTest
{
private static IContainer { get; set; }
[TestInitialize]
public void Initialize()
{
var builder = new ContainerBuilder();
// Use my fake employeeDA for testing
builder.RegisterType<FakeEmployeeDA>().As<IEmployeeDA>();
// Something magical happens
}
[TestMethod]
public void SelectByIdTest(int id) { /* testing stuff */ }
}
What about:
builder.RegisterType<EmployeeDA>().AsSelf().PropertiesAutowired();
see here:
http://code.google.com/p/autofac/wiki/PropertyInjection

Entity Framework 4.1 insert error

i have written a generic repository for my base windows which have a problem with.
lets be more specific, there is a little poco class called Unit as following:
public class Unit : BaseEntity
{
public string Name { get; set; }
private ICollection<Good> _goods;
public virtual ICollection<Good> Goods
{
get
{
if(_goods==null)
{
return new List<Good>();
}
return _goods;
}
set { _goods = value; }
}
}
which is inherited from a base entity class as :
public class BaseEntity
{
public int Id { get; set; }
public override string ToString()
{
return Id.ToString();
}
}
and this is my Add section of generic repository class:
public void Add(TEntity entity)
{
if (entity == null) return;
if (Context.Entry(entity).State == EntityState.Detached)
{
Context.Set<TEntity>().Attach(entity);
}
Context.Set<TEntity>().Add(entity);
Context.SaveChanges();
}
before add a new record, max id is fetched from db and placed in IdTextBox and them add method of base form is called which calls aforementioned Add method of base repository. here is the problem, i get this error, "The property 'Id' is part of the object's key information and cannot be modified."
there is also a mapper class that maps every property to its corresponding control which does its job fine.
What is my problem?
Thanks in advance.
i figured out that this problem is occured because of auto detect changes enability which was true.

asp mvc roles from httpcontext

i want to pass current area name to authorization attribute, like:
[SexyAuthorize(Roles = Url.RequestContext.RouteData.Values["area"])]
public class FormsController : Controller
{
}
but Url is member of controller. how can i pass it other way?
i know that i can use User.InRole in each method, but i want do it for class. thx.
You can't pass dynamic values to an attribute like this. All values passed to an attribute in .NET need to be known at compile time. One possible workaround is to fetch this value in your custom implementation of the attribute as you have access to the HTTP context.
Something like:
[SexyAuthorize(RolesRouteParamName = "area")]
public class FormsController : Controller
{
...
}
and then:
public SexyAuthorizeAttribute: AuthorizeAttribute
{
public string RolesRouteParamName { get; set; }
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
if (httpContext == null)
{
throw new ArgumentNullException("httpContext");
}
var roles = httpContext.Request.RequestContext.RouteData.Value[RolesRouteParamName];
// TODO: continue with the implementation...
...
}
}

MEF Metadata from the exported parts

I'm looking to use MEF for a plugin system for an application I'm building. Each component I want to have an identifier on (a GUID) which I want to be able to look up against. But this ID is also something that is useful when working with the exported part.
Is there a way that I can have a Metadata attribute which contains the ID as well as a property (or method) on the exported part, short of having developers fill it out twice or use reflection to find it from the attribute?
It's likely to be a mixture of a MEF metadata attribute, and an abstract base class. I would define my plugin contract as something like:
public interface IPluginMetadata
{
Guid PluginId { get; }
}
public interface IPlugin : IPluginMetadata
{
void Initialise();
}
I've enforced that the IPlugin interface also inherits our metadata contract IPluginMetadata. Next, we can create a custom export attribute:
[AttributeUsage(AttributeTargets.Class, Inherit = true), MetadataAttribute]
public class ExportPluginAttribute : ExportAttribute, IPluginMetadata
{
public ExportPluginAttribute(string pluginId) : base(typeof(IPlugin))
{
if (string.IsNullOrEmpty(pluginId))
throw new ArgumentException("'pluginId' is required.", "pluginId");
PluginId = new Guid(pluginId);
}
public Guid PluginId { get; private set; }
}
You don't need to decorate the export attribute with the metadata contract IPluginMetadata, as MEF will project the properties anyway, but I prefer to do so, so if I do introduce changes to my metadata contract, then my export attribute should be updated too. No harm, no foul.
Once we've done this, we can define an abstract base class from which to implement our plugin contract:
public abstract class PluginBase : IPlugin
{
protected PluginBase()
{
var attr = GetType()
.GetCustomAttributes(typeof(ExportPluginAttribute), true)
.Cast<ExportPluginAttribute>()
.SingleOrDefault();
PluginId = (attr == null) ? Guid.Empty : attr.PluginId;
}
public virtual Guid PluginId { get; private set; }
public abstract void Initialise();
}
We can then grab the custom attribute through the abstract class's constructor, and apply the property accordingly. That we can do:
public IPlugin GetPlugin(Guid id)
{
var plugin = container
.GetExports<IPlugin, IPluginMetadata>()
.Where(p => p.Metadata.PluginId == id)
.Select(p => p.Value)
.FirstOrDefault();
return plugin;
}
And also:
[ExportPlugin("BE112EA1-1AA1-4B92-934A-9EA8B90D622C")]
public class MyPlugin : PluginBase
{
public override Initialise()
{
Console.WriteLine(PluginId);
}
}
We can see that out PluginId is exposed both through exported metadata, as well as a property of our plugin.
That code is all untested, but I hope it pushes you in the right direction.
Put the GUID in a constant, and use it for both a property and the metadata:
[Export(typeof(IFoo))]
[ExportMetadata("GUID", _guid)]
public class Foo : IFoo
{
private const string _guid = "abc";
public string Guid { get { return _guid; } }
}
Note that you can't use the Guid type instead of string, as that is not permitted by the const keyword.