построение MVC CMS - PullRequest
       22

построение MVC CMS

4 голосов
/ 04 ноября 2010

Мне нужна простая функциональность для добавления страниц / изменения контента на этих страницах.Я посмотрел на n2 и другие готовые инструменты CMS, но это путь к усовершенствованию простой функциональности CMS, которая мне нужна.

Какой лучший подход?У меня уже есть приложение MVC, в которое я хотел бы добавить / создать простые функции, такие как:

  1. указать шаблон
  2. добавить области к этому шаблону
  3. добавить содержимоечерез wysiwyg.

не уверен, с чего начать.

любая информация высоко ценится.спасибо

это для .NET MVC

1 Ответ

10 голосов
/ 04 ноября 2010

Предполагая, что вы используете 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<Page>, вы можете использовать метод 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.ascx, TwoColumnTemplate.ascx и HtmlArea.ascx). Возможно, вы также захотите создать частичное представление для AreaContainer.

Что касается действия контроллера, которое получает отредактированную страницу, все становится немного сложнее. Поскольку Page имеет свойство типа TemplateBase, которое является абстрактным классом, DefaultModelBinder не будет знать, как его заполнить. Вы можете обойти это, написав специальный механизм связывания модели, который каким-то образом «знает», какой класс реализации нужно создать. И как бы он это узнал? Один из вариантов, который я могу придумать, - это включить в представление скрытое поле, содержащее имя фактического типа времени выполнения шаблона страницы. Я полагаю, это что-то вроде хака, но так как вам не хватает простоты, я думаю, все будет в порядке В этом случае просто включите свойство с именем, например, RuntimeTypeName в класс TemplateBase:

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

Поскольку он просто вызывает GetType(), который является виртуальным методом, переопределенным всеми типами по умолчанию, он вернет имя типа шаблона времени выполнения.

Затем вы должны убедиться, что созданные вами частичные представления для реализаций TemplateBase содержат (скрытое) поле для свойства TemplateBase.RuntimeTypeName. Другими словами, в SingleColumnTemplate.ascx и TwoColumnTemplate.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<AreaBase> - и поскольку AreaBase также является абстрактным классом, мы должны применить тот же хак, чтобы он был правильно связан. То есть добавьте свойство RuntimeTypeName к классу AreaBase и зарегистрируйте нашу пользовательскую привязку модели для класса AreaBase в Global.asax.

При условии, что мы выполнили все эти шаги, у нас может быть метод действия на 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(...), создайте соответствующие частичные представления, и все готово.

Для WYSIWYG-редактирования контента вы, вероятно, захотите использовать сторонний компонент. CKEditor , TinyMCE , YUI Rich Text Editor и Telerik Editor - некоторые примеры.

Это был мой взгляд на это! Все комментарии приветствуются; как я уже упоминал, я сам изучаю ASP.NET MVC, и было бы здорово, если бы на мои ошибки указывали люди, которые знают лучше.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...