与类属性参数和插入到 ViewData 中的继承相结合



>问题仅用于教育目的。

我有一个 MVC 应用程序,它有 3 个 Web 旅程,它们在许多方面相互镜像 - 这使得引入处理常见部分的基类变得很方便。

我还使用了许多被放入viewData的类属性 这使得在旅程之间重用视图时很方便,我可以在每个旅程中插入不同的电话号码(或其他位),而无需在每个视图模型中继承此数据,因为它在每个旅程中都是相同的(不能放置在母版页中,因为它显示在许多不同的视图中)。在生成电子邮件等时,这些常量也用于代码中。

[LayoutViewData(ContactNumber = ContactNumber, LegalType = LegalType, LegalReference = LegalReference)]
public class JourneyController : BaseJourneyController
{
    private const string ContactNumber = "0800 161 5191";
    private const string ContactNumberForPricingPage = "0800 051 3322";
    private const string ProductReference = "LS0083";
    private const string LegalReference = "conveyancing";
}

public class LayoutViewDataAttribute : ActionFilterAttribute
{
    public override void OnActionExecuting(ActionExecutingContext filterContext)
    {
        filterContext.Controller.ViewData.Add("ContactNumber", this.ContactNumber);
    }
    ...
}

我正在尝试解决的问题:

有些方法在旅程中几乎相同,可以移动到基数,但它们需要常量中的值。问题是只有静态可以传递到属性中,这意味着我不能简单地将它们更改为仅使用 getter 的抽象属性并继续我的生活。

我的选择:

  1. 更改方法签名以将这些常量传递到方法 - 我不喜欢这样,因为必须将数据传递给 base 表明实现应该在类本身中。

  2. 引入抽象 Get 方法,然后返回常量以在基类中使用,例如。

    protected override string GetContactNumber()
    {
        return ContactNumber;
    }
    

    我不喜欢没有办法强制返回这些特定的常量,并且感觉有点笨拙,但恕我直言,这是花费最少努力的最佳解决方案。

  3. 摆脱常量和属性并引入 BaseViewModel 类,该类将具有抽象属性和每个旅程的一个机制来设置值。或者可以是 3 个基类,并且已经设置了这些属性。- 不喜欢它,很多替代品。

你能想到其他一些解决方案吗,那就是最少的工作和最少的黑客来解决我的情况。

我认为您在这里的主要问题是您将属性用于它们不打算用于的目的。属性用于向代码构造添加元数据。但是,您正在使用它们来附加通常应该是实际对象定义一部分的数据(作为属性获取器)。

此外,我不认为你一定要诉诸类继承来提出一个适用于这种情况的设计。但是,如果需要,此解决方案仍然有效。

独立接口

如果将每段数据分解到其自己的接口中,则可以利用类可以实现多个接口的事实来根据需要重写默认值。

public interface IContactNumber
{
    string ContactNumber { get; }
}

独立式操作钳

与其继承具有自身内置限制的ActionFilterAttribute,不如实现IActionFilter从等式中删除不必要的属性。

public class ContactNumberActionFilter : IActionFilter
{
    private readonly string defaultContactNumber;
    public ContactNumberActionFilter(string defaultContactNumber)
    {
        this.defaultContactNumber = defaultContactNumber;
    }
    public void OnActionExecuted(ActionExecutedContext filterContext)
    {
        // No implementation
    }
    public void OnActionExecuting(ActionExecutingContext filterContext)
    {
        var controller = filterContext.Controller;
        var provider = controller as IContactNumber;
        var contactNumber = (provider == null) ? this.defaultContactNumber : provider.ContactNumber;
        controller.ViewData.Add("ContactNumber", contactNumber);
    }
}

然后,只需全局注册过滤器即可。

public class FilterConfig
{
    public static void RegisterGlobalFilters(GlobalFilterCollection filters)
    {
        filters.Add(new HandleErrorAttribute());
        // Register the action filter globally and provide a default number
        filters.Add(new ContactNumberActionFilter("0800 161 5191"));
        // TODO: Make filters for ContactNumberForPricingPage, 
        // ProductReference, and LegalReference and set their default
        // values here.
    }
}

用法

现在,如果要覆盖默认联系号码,只需在控制器中实现IContactNumber即可。如果未实现此接口,将使用默认编号。

public class HomeController : Controller, IContactNumber
{
    public string ContactNumber
    {
        get { return "0800 161 5192"; }
    }
    public ActionResult Index()
    {
        return View();
    }
}

请注意,如果您确实需要开/关开关,您还可以添加一种方法来打开或关闭此功能,方法是使用 Attribute 并在将ContactNumber添加到ViewData之前测试其是否存在。

[AttributeUsage(AttributeTargets.Class, AllowMultiple = false)]
public class ContactNumberAttribute : Attribute
{
}

在我看来,您正在尝试使用模板模式。

如果我接近这种模式,我会使用一个接口来定义提供"常量"的合约:

public interface IJourneyConstants
{ 
     string ContactNumber {get;}
     string ContactNumberForPricingPage{ get;}
     string ProductReference {get;}
     string LegalReference = {get;}
}

然后,您将定义将提供此类数据的类:

public class ExampleJourneyConstantProvider : IJourneyConstants
{
   string IJourneyConstants.ContactNumber => "0800 161 5191";
   string IJourneyConstants.ContactNumberForPricingPage => "0800 051 3322";
   string IJourneyConstants.ProductReference => "LS0083";
   string IJourneyConstants.LegalReference => "conveyancing";
} 

在此示例中,我使用了显式接口实现。 这不是必需的,但是如果您将其放在Controller中,它会使您的控制器看起来更干净一些。

然后定义一个Controller

 [LayoutViewData(Provider = typeof(ExampleJourneyConstantProvider)]
 public class ExampleJourneyController : Controller{
 }

注意:您的控制器没有理由无法实现IJourneyConstants

 [LayoutViewData(Provider = typeof(ExampleJourneyController )]
 public class ExampleJourneyController : Controller, IJourneyConstants{
 }

然后,您将ActionFilter动态创建提供程序**的实例,强制转换它,应用任何常见的诱人引擎代码并更新 ViewData:

 public class LayoutViewDataAttribute : ActionFilterAttribute
 {
     public override void OnActionExecuting(ActionExecutingContext filterContext)
    {
        IJourneyConstants constants;
         try{
           constants = (IJourneyConstants )System.Activator.CreateInstance(this.Provider);
         }catch (Exception e){
             throw new Exception("Failed to create instance of IJourneyConstants from " + 
                 $"{this.Provider.Name}.  Ensure it inherits from IJourneyConstants "+ 
                  "and contains a public paramaterless constructor.");
         }
         // perform any common calculation using your constants
         bool isContactNumberInternational = constants.ContactNumber.StartsWith("+");
         // update the ViewData
         filterContext.Controller.ViewData.Add("ContactNumber", 
             constants.ContactNumber);
         filterContext.Controller.ViewData.Add("IsNumberInternational", 
             isContactNumberInternational);
   }    
} 

最新更新