Zkeacms for. Net core deep parsing

Time:2021-9-19

Introduction to zkeacms
Zkeacms.core is an open source cms developed based on. Net core MVC. Zkeacms allows users to freely plan page layout, use visual editing design “WYSIWYG”, and drag and drop directly on the page to add content.

Zkeacms uses plug-in design, module separation and horizontal expansion to enrich the functions of CMS.

Responsive design

Zkeacms uses bootstrap 3 grid system to realize responsive design, so that it can be accessed normally on different devices. At the same time, standing on the shoulders of the bootstrap giant, there are rich theme resources to use.

Simple demonstration

Next, let’s look at program design and principle

Project structure

  • Easyframework underlying framework
  • Zkeacms CMS core
  • Zkeacms.article article plugin
  • Zkeacms.product product plug-in
  • Zkeacms.sectionwidget template component plug-in
  • ZKEACMS.WebHost 

Principle – access request process

routeIt plays a key role in zkeacms. The access process direction is determined by the priority of the route. If a matching route is found, the corresponding controller – > action – > View of the route will be followed first. If there is no matching route, the “full capture” route with the lowest route priority will be followed to process the user’s request and finally return the response.

The lowest priority “full capture” route is used to process user created pages. When the request comes in, first go to the database to find out whether the page exists. If it does not exist, it returns 404. After finding the page, find out all the components and contents of the page, and then call the “display” method of each component to get the corresponding “ViewModel” and “view”, and finally display it according to the layout of the page.

Zkeacms request flow chart

Drive page components:


widgetService.GetAllByPage(filterContext.HttpContext.RequestServices, page).Each(widget =>
{
  if (widget != null)
  {
    IWidgetPartDriver partDriver = widget.CreateServiceInstance(filterContext.HttpContext.RequestServices);
    WidgetViewModelPart part = partDriver.Display(widget, filterContext);
    lock (layout.ZoneWidgets)
    {
      if (layout.ZoneWidgets.ContainsKey(part.Widget.ZoneID))
      {
        layout.ZoneWidgets[part.Widget.ZoneID].TryAdd(part);
      }
      else
      {
        layout.ZoneWidgets.Add(part.Widget.ZoneID, new WidgetCollection { part });
      }
    }
    partDriver.Dispose();
  }
});

Page rendering:


foreach (var widgetPart in Model.ZoneWidgets[zoneId].OrderBy(m => m.Widget.Position).ThenBy(m => m.Widget.WidgetName))
{
  <div style="@widgetPart.Widget.CustomStyle">
    <div>
      @if (widgetPart.Widget.Title.IsNotNullAndWhiteSpace())
      {
        <div>
          <div>
            @widgetPart.Widget.Title
          </div>
          <div>
            @Html.DisPlayWidget(widgetPart)
          </div>
        </div>
      }
      else
      {
        @Html.DisPlayWidget(widgetPart)
      }
    </div>
  </div>
}

Pluginbase, the “most critical” class of the plug-in

Each plug-in / module must inherit pluginbase as an entry for plug-in initialization. When the program starts, it will load these classes and do some key initialization work.

public abstract class PluginBase : ResourceManager, IRouteRegister, IPluginStartup
{
  public abstract IEnumerable<RouteDescriptor> RegistRoute(); // The route required to register the plug-in can return null
  public abstract IEnumerable<AdminMenu> AdminMenu(); // The menu provided by the plug-in at the back end can return null
  public abstract IEnumerable<PermissionDescriptor> RegistPermission(); // Permissions to register plug-ins
  public abstract IEnumerable<Type> WidgetServiceTypes(); // Returns the types of all components provided in the plug-in
  public abstract void ConfigureServices(IServiceCollection serviceCollection); // Interface and implementation corresponding to IOC registration
  public virtual void InitPlug(); // Initialize the plug-in and call this method when the program starts
}

For specific implementation, please refer to “article” plug-in articleplug.cs or “product” plug-in productplug.cs

Load the plug-in startup.cs


public void ConfigureServices(IServiceCollection services)
{
  services.UseEasyFrameWork(Configuration).LoadEnablePlugins(plugin =>
  {
    var cmsPlugin = plugin as PluginBase;
    if (cmsPlugin != null)
    {
      cmsPlugin.InitPlug();
    }
  }, null);      
}

Component composition

A page is composed of many components. Each component can contain different content, such as text, pictures, videos, etc. the content is determined by the component and the presentation mode is determined by the component template (view).

The relationship and presentation are roughly as shown in the figure below:

Entity entity

Each component corresponds to an entity, which is used to store some information related to the component. Entity must inherit from the basicwidget class.

For example, the entity class of an HTML component:


[ViewConfigure(typeof(HtmlWidgetMetaData)), Table("HtmlWidget")]
public class HtmlWidget : BasicWidget
{
  public string HTML { get; set; }
}
class HtmlWidgetMetaData : WidgetMetaData<HtmlWidget>
{
  protected override void ViewConfigure()
  {
    base.ViewConfigure();
    ViewConfig(m => m.HTML).AsTextArea().AddClass("html").Order(NextOrder());
  }
}

Metadata configuration [viewconfigure (typeof (htmlwidgetmetadata))] is used in the entity class to control the display of form pages and list pages through simple settings. If set as text or drop-down box; Required, verification of length, etc.

The implementation method here is to add a new modelmetadatadetailsprovider to MVC. The function of this provider is to grab the configuration information of these metadata and submit it to MVC.


services.AddMvc(option =>
  {
    option.ModelMetadataDetailsProviders.Add(new DataAnnotationsMetadataProvider());
  })

Service

Widgetservice is a bridge between data and template. It grabs data through service and sends it to the page template. Service must inherit from widgetservice < widgetbase, cmsdbcontext >. If the business is complex, override the corresponding method of the base class to implement it.

For example, service of HTML component:


public class HtmlWidgetService : WidgetService<HtmlWidget, CMSDbContext>
{
  public HtmlWidgetService(IWidgetBasePartService widgetService, IApplicationContext applicationContext)
    : base(widgetService, applicationContext)
  {
  }
 
  public override DbSet<HtmlWidget> CurrentDbSet
  {
    get
    {
      return DbContext.HtmlWidget;
    }
  }
}

View entity ViewModel

ViewModel is not required. When an entity cannot be transferred to the view as a ViewModel to meet the requirements, you can create a new ViewModel and transfer the ViewModel, which will require rewriting the display method


public override WidgetViewModelPart Display(WidgetBase widget, ActionContext actionContext)
{
  //do some thing
  return widget.ToWidgetViewModelPart(new ViewModel());
}

View / template widget.cshtml

A template is used to display content. The “model” required by the template is collected through the service, and finally displayed by the template.

Dynamic compilation of decentralized templates

The plug-in resources are under their own folders. The default view engine cannot find these views and compile them. The mvc4 version of zkeacms is implemented by rewriting viewengine Net core MVC can be implemented more conveniently. You can implement your own configureoptions < razorviewengineoptions >, and then through dependency injection.


public class PluginRazorViewEngineOptionsSetup : ConfigureOptions<RazorViewEngineOptions>
{
  public PluginRazorViewEngineOptionsSetup(IHostingEnvironment hostingEnvironment, IPluginLoader loader) :
    base(options => ConfigureRazor(options, hostingEnvironment, loader))
  {
 
  }
  private static void ConfigureRazor(RazorViewEngineOptions options, IHostingEnvironment hostingEnvironment, IPluginLoader loader)
  {
    if (hostingEnvironment.IsDevelopment())
    {
      options.FileProviders.Add(new DeveloperViewFileProvider());
    }
    loader.GetPluginAssemblies().Each(assembly =>
    {
      var reference = MetadataReference.CreateFromFile(assembly.Location);
      options.AdditionalCompilationReferences.Add(reference);        
    });
    loader.GetPlugins().Where(m => m.Enable && m.ID.IsNotNullAndWhiteSpace()).Each(m =>
    {
      var directory = new DirectoryInfo(m.RelativePath);
      if (hostingEnvironment.IsDevelopment())
      {
        options.ViewLocationFormats.Add($"/Porject.RootPath/{directory.Name}" + "/Views/{1}/{0}" + RazorViewEngine.ViewExtension);
        options.ViewLocationFormats.Add($"/Porject.RootPath/{directory.Name}" + "/Views/Shared/{0}" + RazorViewEngine.ViewExtension);
        options.ViewLocationFormats.Add($"/Porject.RootPath/{directory.Name}" + "/Views/{0}" + RazorViewEngine.ViewExtension);
      }
      else
      {
        options.ViewLocationFormats.Add($"/{Loader.PluginFolder}/{directory.Name}" + "/Views/{1}/{0}" + RazorViewEngine.ViewExtension);
        options.ViewLocationFormats.Add($"/{Loader.PluginFolder}/{directory.Name}" + "/Views/Shared/{0}" + RazorViewEngine.ViewExtension);
        options.ViewLocationFormats.Add($"/{Loader.PluginFolder}/{directory.Name}" + "/Views/{0}" + RazorViewEngine.ViewExtension);
      }
    });
    options.ViewLocationFormats.Add("/Views/{0}" + RazorViewEngine.ViewExtension);
  }
}

Looking at the above code, you may wonder why you should divide the development environment. This is because the folder directory structure is different when zkeacms is released and developed. In order to facilitate development, special processing of the development environment is added. The next step is to inject this configuration:

services.TryAddEnumerable(ServiceDescriptor.Transient<IConfigureOptions<RazorViewEngineOptions>, PluginRazorViewEngineOptionsSetup>());            

EntityFrameWork

Zkeacms for. Net core uses entityframework as database access. Database related configuration entityframeworkconfigure


public class EntityFrameWorkConfigure : IOnConfiguring
{
  public void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
  {
    optionsBuilder.UseSqlServer(Easy.Builder.Configuration.GetSection("ConnectionStrings")["DefaultConnection"]);
  }
}

The configuration of entity can still be written directly on the corresponding class or attribute. If you want to use the Entity Framework fluent API, create a class that inherits from ionmodelcreating


class EntityFrameWorkModelCreating : IOnModelCreating
{
  public void OnModelCreating(ModelBuilder modelBuilder)
  {
    modelBuilder.Entity<LayoutHtml>().Ignore(m => m.Description).Ignore(m => m.Status).Ignore(m => m.Title);
  }
}

theme

Zkeacms uses bootstrap 3 as the basis and less to determine many variables, such as margin, color, background, etc. you can “compile” your own theme by simply modifying the variables.

Or you can directly use the existing bootstrap 3 theme as the basis, and then quickly create the theme.

last

There are many more about zkeacms. If you are also interested, welcome to join us.

Zkeacms for. Net core is to make building websites easier and faster. The modification and revision of the page has also become easier and convenient.

The above is the whole content of this article. I hope it will be helpful to your study, and I hope you can support developpaer.