【问题标题】:building MVC CMS构建 MVC CMS
【发布时间】:2011-05-05 01:35:37
【问题描述】:

我需要一个简单的功能来添加页面/更改这些页面上的内容。我查看了 n2 和其他预构建 CMS 工具,但这些都是我需要的简单 CMS 功能的高级方法。

最好的方法是什么?我已经有一个 MVC 应用程序,我想添加/构建一个简单的功能,例如:

  1. 指定模板
  2. 向该模板添加区域
  3. 通过所见即所得添加内容。

不知道从哪里开始。

非常感谢任何信息。 谢谢

这是用于 .NET MVC 的

【问题讨论】:

  • 或者,更好的是,“您使用的是什么平台”——我们可能会假设 .NET,但在您告诉我们之前我们无法真正回答
  • 自从他看过 N2 之后,我认为很明显他正在使用 ASP.NET。考虑到这一点,许多人(尤其是初学者)似乎将“MVC”一词视为 ASP.NET MVC 框架的同义词。但我只是猜测。

标签: asp.net-mvc content-management-system wysiwyg template-engine


【解决方案1】:

假设您使用的是 ASP.NET MVC,并且希望保持简单,那么这样的事情如何:

public abstract class TemplateBase
{
    public abstract string TemplateName { get; }
}

public class SingleColumnTemplate : TemplateBase
{
    public override string TemplateName { get { return "Single-column page"; } }
    public AreaContainer CenterColumn { get; protected set; }

    public SingleColumnTemplate()
    {
        CenterColumn = new AreaContainer("Center column");
    }
}

public class TwoColumnTemplate : TemplateBase
{
    public override string TemplateName { get { return "Two-column page"; } }
    public AreaContainer LeftColumn { get; protected set; }
    public AreaContainer RightColumn { get; protected set; }

    public TwoColumnTemplate()
    {
        LeftColumn = new AreaContainer("Left column");
        RightColumn = new AreaContainer("Right column");
    }
}

// TODO Add more template types

public class AreaContainer
{
    public string ContainerName { get; set; }
    public IList<AreaBase> Areas { get; protected set; }

    public AreaContainer(string name)
    {
        ContainerName = name;
        Areas = new List<AreaBase>();
    }
}

public abstract class AreaBase
{
    public abstract string AreaName { get; }
}

public class HtmlArea : AreaBase
{
    public override string AreaName { get { return "HTML content"; } }
    public string HtmlContent { get; set; }
}

// TODO Add more area types

public class Page
{
    public int Id { get; set; }
    public string Title { get; set; }
    public TemplateBase Template { get; set; }
}

用于编辑现有页面的控制器操作可能类似于:

public class PageAdminController : Controller
{
    [HttpGet]
    ActionResult Edit(int id) 
    {
        var page = GetPageFromStorageById(id);
        // TODO If the page is not found, issue 404
        return View(page);
    }

    // ...
}

在视图(Views/PageAdmin/Edit.aspx)中,它应该被强类型化为ViewPage&lt;Page&gt;,你可以使用HtmlHelper.EditorFor(...)方法来渲染合适的模板视图,前提是你已经为每个模板类型创建了一个局部视图:

<!-- Inside the edit view for Page (Edit.aspx) -->
<%: Html.HiddenFor(m => m.Id) %>
<%: Html.EditorFor(m => m.Title) %>
<%: Html.EditorFor(m => m.Template) %>

在文件夹Views/PageAdmin/EditorTemplates 中,您可以为每个模板和区域类型(即SingleColumnTemplate.ascxTwoColumnTemplate.ascxHtmlArea.ascx)放置部分编辑视图。您可能还想为AreaContainer 创建一个局部视图。

至于接收编辑页面的控制器动作,事情变得有点复杂。由于Page 有一个TemplateBase 类型的属性,它是一个抽象类,DefaultModelBinder 不知道如何填充它。您可以通过编写自定义模型绑定器来解决此问题,该绑定器以某种方式“知道”要实例化哪个实现类。它怎么会知道呢?我能想到的一种选择是在视图中包含一个隐藏字段,该字段包含页面模板的实际运行时类型的名称。我想这有点像黑客,但既然你追求简单,我认为没关系。在这种情况下,只需在 TemplateBase 类中包含一个名为 RuntimeTypeName 的属性:

public string RuntimeTypeName { get { return GetType().FullName; } }

由于它只是调用GetType(),这是一个默认被所有类型覆盖的虚方法,它会返回运行时模板类型的名称。

然后,您必须确保为 TemplateBase 实现创建的部分视图包含 TemplateBase.RuntimeTypeName 属性的(隐藏)字段。换句话说,在SingleColumnTemplate.ascxTwoColumnTemplate.ascx 中你会有这样的一行:

<%: Html.HiddenFor(m => m.RuntimeTypeName) %>

利用此信息创建正确类型的模板的模型绑定器可能如下所示:

/// <summary>
/// Model binder hack that builds upon the DefaultModelBinder, 
/// but that can detect the "proper" subclass/implementing class 
/// type for a model, assuming the name of that type is contained
/// in a field called "RuntimeTypeName".
/// </summary>
public class InheritanceSupportingModelBinder : DefaultModelBinder
{
    // Assume that the name of the field that contains the 
    // runtime type name is called "RuntimeTypeName"
    public const string RuntimeTypeNameField = "RuntimeTypeName";
    private Type RuntimeType { get; set; }

    // This method is called by the DefaultModelBinder to find out which
    // properties of the current model that it should attempt to bind
    protected override PropertyDescriptorCollection GetModelProperties(
        ControllerContext controllerContext, ModelBindingContext bindingContext)
    {
        // If we have found out the runtime type of the model through
        // looking at the "special" field above, use the properties of that type.
        // Otherwise, use the default behavior.
        if (RuntimeType != null)
        {
            return TypeDescriptor.GetProperties(RuntimeType);
        }
        else
        {
            return base.GetModelProperties(controllerContext, bindingContext);
        }
    }

    // This method is called by the DefaultModelBinder when it 
    // tries to create an instance of the model class. If the 
    // class is abstract, an exception will be thrown. Therefore
    // we try to read the name of the actual type from the 
    // RuntimeTypeName (hidden) field and return an instance of that type.
    protected override object CreateModel(ControllerContext controllerContext, 
                                          ModelBindingContext bindingContext, 
                                          Type modelType)
    {
        if (bindingContext.ValueProvider.ContainsPrefix(
            bindingContext.ModelName + "." + RuntimeTypeNameField))
        {
            var result = bindingContext.ValueProvider.GetValue(
                bindingContext.ModelName + "." + RuntimeTypeNameField);

            if (result != null && !string.IsNullOrEmpty(result.AttemptedValue))
            {
                // Check that the type indicated by the hidden field is really
                // a subclass of (or implementing) the indicated base class
                var tempType = Type.GetType(result.AttemptedValue);
                if (modelType.IsAssignableFrom(tempType))
                {
                    RuntimeType = modelType = tempType;
                }
            }
        }
        return base.CreateModel(controllerContext, bindingContext, modelType);
    }
}

免责声明:我自己是 ASP.NET MVC 的初学者,所以这个模型绑定器很可能有问题。我通过查看DefaultModelBinder 的源代码和反复试验将其组合在一起。这只是一个示例,但根据我的(快速而肮脏的)测试,它似乎有效。

当然,您需要在 Global.asax 中注册它才能启动:

ModelBinders.Binders.Add(
    typeof(TemplateBase), 
    new InheritanceSupportingModelBinder());

但我们还没有完成!请记住 AreaContainer.Areas 集合的类型是 IList&lt;AreaBase&gt; - 由于 AreaBase 也是一个抽象类,我们必须应用相同的 hack 才能正确绑定它。也就是说,将RuntimeTypeName 属性添加到AreaBase 类,并在Global.asax 中为AreaBase 类注册我们的自定义模型绑定器。

如果到目前为止我们已经遵循了所有这些步骤,我们就可以在 PageAdminController 上设置一个操作方法来处理看起来像这样的页面编辑:

[HttpPost]
public ActionResult Edit(Page page)
{
    if (!ModelState.IsValid)
    {
        return View(page);
    }
    // TODO Save page to database or whatever
    // TODO Redirect to page index
}

创建新页面的操作方法留作练习,应该不难(用户从列表中选择模板,显示正确的表单,后处理操作如上)。

显示页面应该是微不足道的,只需使用HtmlHelper.DisplayFor(...) 而不是EditorFor(...),创建相应的局部视图就可以了。

对于所见即所得的内容编辑,您可能需要使用第三方组件。 CKEditorTinyMCEYUI Rich Text EditorTelerik Editor 是一些示例。

这就是我的看法!欢迎所有的cmets;正如我提到的,我自己正在学习 ASP.NET MVC,如果我的错误能被更了解的人指出,那就太好了。

【讨论】:

  • @Shane 太糟糕了,你从这样的人那里拿走了重要的工作,甚至懒得不时点击投票/接受作为回报。
  • 就创建新页面而言。这些是新生成的页面,还是您有一个使用 db 内容动态更新的页面?
  • 这行得通,我成功地将 Page 对象重新绑定。问:Page类有抽象类TemplateBase,如何访问HtmlArea内容?
猜你喜欢
  • 2010-10-13
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-08-24
  • 2014-08-02
  • 2011-03-30
  • 1970-01-01
  • 2010-11-25
相关资源
最近更新 更多