Версия DNN на момент выхода статьи - DotNetNuke Community Edition 05.06.01
Периодически требуется создать веб сайты за короткий срок, с каким-то небольшим функционалом или вообще без оного – так называемый «Сайт-Визитка», и чтобы после развертывания было можно просто забыть о нем, предоставив пользователям максимум возможностей - добавлять и удалять страницы, изменять контент и дизайн.
В качестве CMS, для таких задач, я использую Dot Net Nuke, которая построена на технологии Microsoft Asp.Net . С ее помощью можно предоставить все вышеперечисленные возможности и много больше. Так же, благодаря огромному сообществу, можно покупать и продавать модули и шкурки на snowcovered.com .В данной статье представлены только базовые сведения о создании модулей, с точки зрения ASP.net разработчика.
Если за дело берется разработчик мало знакомый с DNN, то он знает, как написать требуемый функционал, но внедрить его в приложение будет проблематично. Далее мы как раз и пойдем по предложенному пути: определим задачу, создадим веб приложение, а после на его основе создадим установочный пакет модуля.
Задача:
Требуется написать DNN модуль, который будет при заходе пользователя на страницу отображать его аватар и произвольную надпись под ним, если же аватар и надпись не задана, то отображать значения по умолчанию.
Проект:
Создадим новый проект Asp.Net Empty Web Application с названием MyAvatar. Далее добавим класс Avatar.cs который будет представлять сущность аватара, вот его код:
- namespace MyAvatar
- {
- internal class Avatar
- {
- public int UserId { get; set; }
- public byte[] Img{ get; set; }
- private string _text = "none";
- public string Text
- {
- get { return _text; }
- set { _text = value; }
- }
- private bool _isNewEntry = true;
- public bool IsNewEntry
- {
- get { return _isNewEntry; }
- set { _isNewEntry = value; }
- }
- public string ImageUrl
- {
- get
- {
- string url = @"~/noAvatar.jpg";
- if (Img != null) url = string.Format(@"~/Image.aspx?id={0}", UserId);
- return url;
- }
- }
- }
- }
Свойства: UserId – уникально идентифицирует каждого пользователя, Img – картинка аватара для пользователя, Text – подпись под аватаром, IsNewEntry – определяет новая ли это запись или данные восстановлены из базы данных, ImageUrl – предоставляет ссылку на картинку аватара.
Обратим внимание на свойство ImageUrl, в нем определен файл noAvatar.jpg, который нужно добавить к проекту в корневой каталог (взять можно из исходников), а так же указана страница Image.aspx. В данном случае, в академических целях, проще хранить картинки в базе, чем работать с файловой системой, данная страница предоставляет картинки из базы по id.
Создайте базу данных с именем MyAvatarDB. Из предыдущего листинга видно, что для хранения состояния модуля достаточно трех полей UserId, Text, Img, остальные являются вычисляемыми. Далее создадим таблицу и CRUD процедуры, листинг:
- USE MyAvatarDB
- GO
- CREATE TABLE MyAvatarTable(
- UserId int NOT NULL PRIMARY KEY,
- Text nvarchar(50) NOT NULL,
- Image varbinary(max) NULL)
- GO
- CREATE PROCEDURE CreateMyAvatar
- @UserId int,
- @Text nvarchar(50),
- @Image varbinary(max) = NULL
- AS
- BEGIN
- INSERT MyAvatarTable (UserId,Text,Image)
- VALUES (@UserId,@Text,@Image)
- END
- GO
- CREATE PROCEDURE DeleteMyAvatar
- @UserId int
- AS
- BEGIN
- DELETE MyAvatarTable
- WHERE UserId = @UserId
- END
- GO
- CREATE PROCEDURE GetMyAvatarByUserId
- @UserId int
- AS
- BEGIN
- SELECT * FROM MyAvatarTable
- WHERE UserId = @UserId
- END
- GO
- CREATE PROCEDURE UpdateMyAvatar
- @UserId int,
- @Text nvarchar(50),
- @Image varbinary(max) = NULL
- AS
- BEGIN
- UPDATE MyAvatarTable
- SET Text = @Text, Image = @Image
- WHERE UserId = @UserId
- END
- GO
Теперь определим строку подключения к базе данных в файле web.config:
<connectionstrings>
<add name="MyAvatarConString" connectionstring="Data Source=.\SQLExpress;Initial Catalog=MyAvatarDB;Integrated Security=SSPI;">
</add>
</connectionstrings>
* This source code was highlighted with Source Code Highlighter.
Добавим в проект новый класс, который будет являться репозиторием с именем AvatarRepository.cs. код:
- using System;
- using System.Configuration;
- using System.Data;
- using System.Data.SqlClient;
- namespace MyAvatar
- {
- internal static class AvatarRepository
- {
- private static SqlConnection BuildConnection()
- {
- return new SqlConnection(ConfigurationManager.ConnectionStrings["MyAvatarConString"].ConnectionString);
- }
- public static Avatar GetById(int userId)
- {
- Avatar avatar = new Avatar { UserId = userId };
- using (SqlConnection conn = BuildConnection())
- {
- SqlCommand command = conn.CreateCommand();
- command.CommandText = "GetMyAvatarByUserId";
- command.CommandType = CommandType.StoredProcedure;
- command.Parameters.AddWithValue("UserId", userId);
- conn.Open();
- var result = command.ExecuteReader();
- result.Read();
- if (result.HasRows)
- {
- avatar.IsNewEntry = false;
- avatar.Text = result["Text"].ToString();
- if (result["Image"].GetType() != typeof(DBNull))
- avatar.Img = (byte[])result["Image"];
- conn.Close();
- }
- }
- return avatar;
- }
- public static void SaveOrCreate(Avatar avatar)
- {
- using (SqlConnection conn = BuildConnection())
- {
- SqlCommand command = conn.CreateCommand();
- command.CommandText = (avatar.IsNewEntry ? "CreateMyAvatar" : "UpdateMyAvatar");
- command.CommandType = CommandType.StoredProcedure;
- command.Parameters.AddWithValue("UserId", avatar.UserId);
- command.Parameters.AddWithValue("Text", avatar.Text);
- command.Parameters.AddWithValue("Image", avatar.Img);
- conn.Open();
- command.ExecuteNonQuery();
- conn.Close();
- }
- }
- public static void DeleteById(int id)
- {
- using (SqlConnection conn = BuildConnection())
- {
- SqlCommand command = conn.CreateCommand();
- command.CommandText = "DeleteMyAvatar";
- command.CommandType = CommandType.StoredProcedure;
- command.Parameters.AddWithValue("UserId", id);
- conn.Open();
- command.ExecuteNonQuery();
- conn.Close();
- }
- }
- }
- }
Единственное на что следует обратить внимание – при вызове метода GetById изначально создается новый экземпляр класса аватар и при его создании свойство IsNewEntry принимает значение true. После чего происходит попытка чтения данных из базы, и при успешной попытке данному свойству выставляется значение false, после чего, в методе SaveOrCreate на основании этого свойства, определяется имя процедуры для создания новой записи или же обновления существующей.
Добавим в проект новую страницу, с названием Image.aspx, о которой мы говорили ранее. Разметка страницы:
<%@ Page Language="C#" CodeBehind="Image.aspx.cs" Inherits="MyAvatar.Image" %>
* This source code was highlighted with Source Code Highlighter.
Код страницы:
- using System;
- namespace MyAvatar
- {
- public partial class Image : System.Web.UI.Page
- {
- protected override void OnLoad(EventArgs e)
- {
- int userId;
- if (Request["id"] != null && int.TryParse(Request["id"], out userId))
- {
- Avatar avatar = AvatarRepository.GetById(userId);
- if (avatar.Img != null)
- {
- Response.Clear();
- Response.ContentType = "image/jpeg";
- Response.BinaryWrite(avatar.Img);
- Response.End();
- }
- }
- }
- }
- }
Здесь все довольно просто, мы получаем из базы данных, по идентификатору, экземпляр класса аватар, проверяем задано ли у него свойство Img и отображаем его.
Далее добавим в проект новый контрол с именем MyAvatar.ascx, который будет исполнять роль View. Разметка:
<%@ Control Language="C#" AutoEventWireup="true" CodeBehind="MyAvatar.ascx.cs" Inherits="MyAvatar.MyAvatar" %>
<link href="AvatarStyle.css" rel="stylesheet" type="text/css" />
<div class="avatarDiv">
<asp:Image runat="server" ID="avatarImage" />
<br />
<asp:Label runat="server" ID="avatarText" />
</div>
* This source code was highlighted with Source Code Highlighter.
Код:
- using System;
- namespace MyAvatar
- {
- public partial class MyAvatar : System.Web.UI.UserControl
- {
- public int UserId;
- protected override void OnInit(EventArgs e)
- {
- Load += MyAvatarLoad;
- base.OnInit(e);
- }
- void MyAvatarLoad(object sender, EventArgs e)
- {
- if (!IsPostBack)
- {
- Avatar avatar = AvatarRepository.GetById(UserId);
- avatarImage.ImageUrl = avatar.ImageUrl;
- avatarText.Text = avatar.Text;
- }
- }
- }
- }
В данном случае мы открываем свойство UserId, для того чтобы его можно было использовать в разметке, после чего восстанавливаем из базы значения и задаем советующие свойства.
Так же добавим к проекту файл AvatarStyle.css :
- .avatarDiv
- {
- border: 1px Solid Black;
- width:90px;
- }
Добавим к проекту новую страницу с названием Edit.aspx, которая будет отвечать за редактирование записи. Разметка:
<%@ Page Language="C#" CodeBehind="Edit.aspx.cs" Inherits="MyAvatar.Edit" %>
<title></title>
<form id="form1" runat="server">
<div>
<asp:image runat="server" id="Photo"></asp:image>
<asp:fileupload runat="server" id="FUploader"></asp:fileupload>
<asp:textbox runat="server" id="Text"></asp:textbox>
<asp:button runat="server" id="SubmitBtn" text="Save"></asp:button>
</div>
</form>
* This source code was highlighted with Source Code Highlighter.
Код страницы:
- using System;
- namespace MyAvatar
- {
- public partial class Edit : System.Web.UI.Page
- {
- private Avatar _avatar;
- protected override void OnInit(EventArgs e)
- {
- Load += EditLoad;
- SubmitBtn.Click += SubmitBtnClick;
- base.OnInit(e);
- }
- void EditLoad(object sender, EventArgs e)
- {
- int userId;
- if (int.TryParse(Request["id"], out userId))
- {
- _avatar = AvatarRepository.GetById(userId);
- if (!Page.IsPostBack) Text.Text = _avatar.Text;
- Photo.ImageUrl = _avatar.ImageUrl;
- }
- }
- protected void SubmitBtnClick(object sender, EventArgs e)
- {
- if (_avatar != null)
- {
- if (FUploader.HasFile)
- {
- _avatar.Img = FUploader.FileBytes;
- }
- _avatar.Text = Text.Text;
- AvatarRepository.SaveOrCreate(_avatar);
- Photo.ImageUrl = _avatar.ImageUrl;
- }
- }
- }
- }
И так же добавим новую страницу Index.aspx, и установим ее как страницу по умолчанию. Разметка страницы:
<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="Index.aspx.cs" Inherits="MyAvatar.Index" %>
<%@ Register TagName="Avatar" TagPrefix="My" Src="~/MyAvatar.ascx" %>
<form id="form1" runat="server">
<div>
<my:avatar runat="server" userid="0"></my:avatar>
<my:avatar runat="server" userid="1"></my:avatar>
<my:avatar runat="server" userid="2"></my:avatar>
</div>
</form>
* This source code was highlighted with Source Code Highlighter.
Как видно из разметки, мы определили три экземпляра нашего контрола с различными UserId, для функционального тестирования, в реальном же приложении UserId у нас будет только один, у каждого пользователя свой.
Запустим наше приложение и браузер отобразит следующую картину:
Далее прейдем по сылкам Edit.aspx?id=0 и Edit.aspx?id=1 и выставим картинки
с соответствующими надписями. После чего Index.aspx отобразит:
Отлично, мы создали требуемый функционал, пора переходить к самому интересному - создать модуль на его основе.
Если у вас по какой-либо причине не получилось дойти до этого шага, то можете взять готовое приложение из исходников и изучить его работу, так же в исходниках вы можете найти все картинки используемые мной ранее.
Создание модуля.
Далее предполагается, что у вас уже стоит установленной CMS Dot Net Nuke, так же установлено Visual Studio Starter Kit (используемый в данной статье Visual Studio Starter Kit).
Создадим новый проект DotNetNuke Compiled Module, с названием AvatarSample
В свойствах проекта выставим Assembly Name & Default Namespace значением ByMegano.AvatarSample, а так же Target framework .net 3.5 Далее весь автоматически сгенерированный код находится в пространстве имен YourCompany.Modules.AvatarSample, я воспользуюсь Resharper’om чтобы переместить его в ByMegano.AvatarSample, вы же можете ничего не делать, так как все равно в последствии мы полностью заменим весь код.
Далее полностью удаляем каталог Documentation и так как мы не собираемся заниматься локализацией то и App_LocalResources. К тому же в нашем проекте присутствует сразу две битых ссылки на библиотеки – DotNetNuke & Microsoft.ApplicationBlocks.Data, необходимо обновить эти ссылки через Browse, требуемые библиотеки находятся в папке bin,предварительно установленной CMS DNN.
Осмотрим файл AvatarSampleInfo.cs он представляет код сущности модуля, совсем как класс Avatar в нашем приложении, на самом деле так и есть, только в DNN сущность принято именовать Info, полностью заменим код данного файла:
- namespace ByMegano.AvatarSample.Components
- {
- public class AvatarSampleInfo
- {
- private string _text = "none";
- private bool _isNewEntry = true;
- public byte[] Img { get; set; }
- public int UserId { get; set; }
- public string Text
- {
- get { return _text; }
- set { _text = value; }
- }
- public bool IsNewEntry
- {
- get { return _isNewEntry; }
- set { _isNewEntry = value; }
- }
- public string ImageUrl
- {
- get
- {
- string url = @"~/DesktopModules/Avatar/noAvatar.jpg";
- if (Img != null) url = string.Format(@"~/DesktopModules/Avatar/Image.aspx?id={0}", UserId);
- return url;
- }
- }
- }
- }
Обратите внимание на свойство ImageUrl, все модули устанавливаются в папку DesktopModules, так же, позже мы определим каталог установки модуля – Avatar, исходя из этого, мы можем точно сказать, где будут находиться файлы и прописать путь.
Далее обратим внимание на файл DataProvider.cs, DNN теоритически может работать не только с SQL Server, но так же с любым другим источником данных, этот класс определяет методы(#region "Abstract methods"), которые будут обязаны реализовать все провайдеры. Так же он отвечает за создание соответствующего провайдера (метод CreateProvider), используя паттерн Single Tone. Полностью заменим код данного файла:
- using System.Data;
- namespace ByMegano.AvatarSample.Components
- {
- public abstract class DataProvider
- {
- #region "Shared/Static Methods"
- private static DataProvider objProvider = null;
- static DataProvider()
- {
- CreateProvider();
- }
- private static void CreateProvider()
- {
- objProvider = (DataProvider)DotNetNuke.Framework.Reflection.CreateObject("data", "ByMegano.AvatarSample.Components", "");
- }
- public static DataProvider Instance()
- {
- return objProvider;
- }
- #endregion
- #region "Abstract methods"
- public abstract IDataReader GetById(int userId);
- public abstract void SaveOrCreate(AvatarSampleInfo avatar);
- public abstract void DeleteById(int id);
- #endregion
- }
- }
Перейдем к файлу SqlDataProvider.cs, он представляет из себя реализацию провайдера для MS SQL Server, от нас требуется реализовать абстрактные методы класса DataProvider, а так же определить спецификатор модуля, который нужен для составления полных имен в базе данных. При вызове хранимых процедур будет составлено полное имя, учитывая данный спецификатор(полное имя составляется по правилам определенным в методе GetFullyQualifiedName по умолчанию: DatabaseOwner + ObjectQualifier + ModuleQualifier + name). Полностью заменим файл следующим кодом:
- using System;
- using System.Data;
- using DotNetNuke.Common.Utilities;
- using DotNetNuke.Framework.Providers;
- using Microsoft.ApplicationBlocks.Data;
- namespace ByMegano.AvatarSample.Components
- {
- public class SqlDataProvider : DataProvider
- {
- #region "Private Members"
- private const string ProviderType = "data";
- private const string ModuleQualifier = "ByMegano_";
- private ProviderConfiguration _providerConfiguration = ProviderConfiguration.GetProviderConfiguration(ProviderType);
- private string _connectionString;
- private string _providerPath;
- private string _objectQualifier;
- private string _databaseOwner;
- #endregion
- #region "Constructors"
- public SqlDataProvider()
- {
- Provider objProvider = (Provider)_providerConfiguration.Providers[_providerConfiguration.DefaultProvider];
- _connectionString = Config.GetConnectionString();
- if (_connectionString == "")
- {
- _connectionString = objProvider.Attributes["connectionString"];
- }
- _providerPath = objProvider.Attributes["providerPath"];
- _objectQualifier = objProvider.Attributes["objectQualifier"];
- if (_objectQualifier != "" & _objectQualifier.EndsWith("_") == false)
- {
- _objectQualifier += "_";
- }
- _databaseOwner = objProvider.Attributes["databaseOwner"];
- if (_databaseOwner != "" & _databaseOwner.EndsWith(".") == false)
- {
- _databaseOwner += ".";
- }
- }
- #endregion
- #region "Properties"
- public string ConnectionString
- {
- get { return _connectionString; }
- }
- public string ProviderPath
- {
- get { return _providerPath; }
- }
- public string ObjectQualifier
- {
- get { return _objectQualifier; }
- }
- public string DatabaseOwner
- {
- get { return _databaseOwner; }
- }
- #endregion
- #region "Private Methods"
- private string GetFullyQualifiedName(string name)
- {
- return DatabaseOwner + ObjectQualifier + ModuleQualifier + name;
- }
- private object GetNull(object Field)
- {
- return DotNetNuke.Common.Utilities.Null.GetNull(Field, DBNull.Value);
- }
- #endregion
- #region "Public Methods"
- public override IDataReader GetById(int userId)
- {
- return (IDataReader)SqlHelper.ExecuteReader(ConnectionString, GetFullyQualifiedName("GetMyAvatarByUserId"), userId);
- }
- public override void SaveOrCreate(AvatarSampleInfo avatar)
- {
- SqlHelper.ExecuteNonQuery(ConnectionString, GetFullyQualifiedName(avatar.IsNewEntry ? "CreateMyAvatar" : "UpdateMyAvatar"), avatar.UserId, avatar.Text, avatar.Img);
- }
- public override void DeleteById(int id)
- {
- SqlHelper.ExecuteNonQuery(ConnectionString, GetFullyQualifiedName("DeleteMyAvatar"), id);
- }
- #endregion
- }
- }
Перейдем к последнему файлу в этой папке - AvatarSampleController.cs, который, является репозиторием, работающим с абстрактным классом DataProvider. Вот его реализация:
- using System;
- using System.Data;
- namespace ByMegano.AvatarSample.Components
- {
- public class AvatarController
- {
- #region "Public Methods"
- public AvatarSampleInfo GetAvatar(int userId)
- {
- AvatarSampleInfo result = new AvatarSampleInfo();
- IDataReader dr = DataProvider.Instance().GetById(userId);
- if (dr.Read())
- {
- result.IsNewEntry = false;
- result.UserId = (int)dr["UserId"];
- result.Text = dr["Text"].ToString();
- if (dr["Image"].GetType() != typeof(DBNull)) result.Img = (byte[])dr["Image"];
- }
- return result;
- }
- public void SaveOrCreate(AvatarSampleInfo objAvatar)
- {
- DataProvider.Instance().SaveOrCreate(objAvatar);
- }
- public void DeleteAvatar(int userId)
- {
- DataProvider.Instance().DeleteById(userId);
- }
- #endregion
- }
- }
Теперь перейдем к файлам корневого каталога, добавим в проект файл noAvatar.jpg и файл и AvatarStyle.css, далее мы должны переименовать файл AvatarStyle.css в module.css в этом случае, при установке, система подхватит его автоматически. Так как наш модуль не требует дополнительных настроек, удалим файл Settings.ascx.
Добавим в проект новую страницу с именем Image.aspx, разметка:
<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="Image.aspx.cs" Inherits="ByMegano.AvatarSample.Image" %>
* This source code was highlighted with Source Code Highlighter.
Код:
- using System;
- using ByMegano.AvatarSample.Components;
- namespace ByMegano.AvatarSample
- {
- public partial class Image : System.Web.UI.Page
- {
- protected override void OnLoad(EventArgs e)
- {
- AvatarController controller = new AvatarController();
- int userId;
- if (Request["id"] != null && int.TryParse(Request["id"], out userId))
- {
- AvatarSampleInfo avatar = controller.GetAvatar(userId);
- if (avatar.Img != null)
- {
- Response.Clear();
- Response.ContentType = "image/jpeg";
- Response.BinaryWrite(avatar.Img);
- Response.End();
- }
- }
- }
- }
- }
Как видите, мы работаем с контроллером.
Полностью заменим разметку файла ViewAvatarSample.ascx на:
<%@ Control Language="C#" Inherits="ByMegano.AvatarSample.ViewAvatarSample" AutoEventWireup="true" CodeBehind="ViewAvatarSample.ascx.cs" %>
<div class="avatarDiv">
<asp:image runat="server" id="avatarImage"></asp:image>
<asp:label runat="server" id="avatarText"></asp:label>
</div>
<asp:hyperlink id="EditLink" navigateurl='<%# EditUrl("UserId",this.UserId.ToString()) %>' runat="server">
Edit
<asp:image runat="server" imageurl="~/images/edit.gif" alternatetext="Edit">
</asp:image>
</asp:hyperlink>
* This source code was highlighted with Source Code Highlighter.
Здесь стоит обратить внимание на две вещи - Мы воспользовались методом DNN EditUrl чтобы получить ссылку на страницу редактирования модуля. И отобразили рисунок из папки images, которая так же принадлежит DNN.
Код:
- using System;
- using ByMegano.AvatarSample.Components;
- using DotNetNuke.Entities.Modules;
- namespace ByMegano.AvatarSample
- {
- partial class ViewAvatarSample : PortalModuleBase
- {
- protected override void OnInit(EventArgs e)
- {
- Load += MyAvatarLoad;
- base.OnInit(e);
- }
- protected override void OnPreRender(EventArgs e)
- {
- if (!IsPostBack) EditLink.DataBind();
- base.OnPreRender(e);
- }
- void MyAvatarLoad(object sender, EventArgs e)
- {
- if (!IsPostBack)
- {
- AvatarController controller = new AvatarController();
- AvatarSampleInfo avatar = controller.GetAvatar(this.UserId);
- avatarImage.ImageUrl = avatar.ImageUrl;
- avatarText.Text = avatar.Text;
- }
- }
- }
- }
Теперь файл EditAvatarSample.ascx, разметка:
<%@ Control Language="C#" AutoEventWireup="true" CodeBehind="EditAvatarSample.ascx.cs" Inherits="ByMegano.AvatarSample.EditAvatarSample" %>
<div>
<asp:image runat="server" id="Photo"></asp:image>
<asp:fileupload runat="server" id="FUploader"></asp:fileupload>
<asp:textbox runat="server" id="Text"></asp:textbox>
<asp:button runat="server" id="SubmitBtn" text="Save"></asp:button>
</div>
* This source code was highlighted with Source Code Highlighter.
Код:
- using System;
- using ByMegano.AvatarSample.Components;
- using DotNetNuke.Common;
- using DotNetNuke.Entities.Modules;
- namespace ByMegano.AvatarSample
- {
- partial class EditAvatarSample : PortalModuleBase
- {
- private AvatarSampleInfo _avatar;
- private AvatarController _controller;
- protected override void OnInit(EventArgs e)
- {
- _controller = new AvatarController();
- Load += EditLoad;
- SubmitBtn.Click += SubmitBtnClick;
- base.OnInit(e);
- }
- void EditLoad(object sender, EventArgs e)
- {
- if ((Request.QueryString["UserId"] != null))
- {
- int userId;
- int.TryParse(Request.QueryString["UserId"], out userId);
- _avatar = _controller.GetAvatar(userId);
- if (_avatar.UserId == 0) _avatar.UserId = userId;
- if (!Page.IsPostBack) Text.Text = _avatar.Text;
- Photo.ImageUrl = _avatar.ImageUrl;
- }
- }
- protected void SubmitBtnClick(object sender, EventArgs e)
- {
- if (_avatar != null)
- {
- if (FUploader.HasFile)
- {
- _avatar.Img = FUploader.FileBytes;
- }
- _avatar.Text = Text.Text;
- _controller.SaveOrCreate(_avatar);
- Response.Redirect(Globals.NavigateURL(), true);
- }
- }
- }
- }
Здесь стоит отметить метод Response.Redirect(Globals.NavigateURL(), true), вызов которого перенаправит вас на страницу, с исходным View(где вы нажали ссылку Edite).
Файл 01.00.00.SqlDataProvider, будет выполнен в момент установки модуля, туда следует поместить SQL код для базы данных:
- GO
- CREATE TABLE {databaseOwner}[{objectQualifier}ByMegano_MyAvatarTable](
- [UserId] [int] NOT NULL,
- [Text] [nvarchar](50) NOT NULL,
- [Image] [varbinary](max) NULL,
- CONSTRAINT [PK_MyAvatarTable_1] PRIMARY KEY CLUSTERED
- (
- [UserId] ASC
- )WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
- ) ON [PRIMARY]
- GO
- CREATE PROCEDURE {databaseOwner}[{objectQualifier}ByMegano_CreateMyAvatar]
- @UserId int,
- @Text nvarchar(50),
- @Image varbinary(max) = NULL
- AS
- BEGIN
- INSERT ByMegano_MyAvatarTable (UserId,Text,Image)
- VALUES (@UserId,@Text,@Image)
- END
- GO
- CREATE PROCEDURE {databaseOwner}[{objectQualifier}ByMegano_DeleteMyAvatar]
- @UserId int
- AS
- BEGIN
- DELETE ByMegano_MyAvatarTable
- WHERE UserId = @UserId
- END
- GO
- CREATE PROCEDURE {databaseOwner}[{objectQualifier}ByMegano_GetMyAvatarByUserId]
- @UserId int
- AS
- BEGIN
- SELECT * FROM ByMegano_MyAvatarTable
- WHERE UserId = @UserId
- END
- GO
- CREATE PROCEDURE {databaseOwner}[{objectQualifier}ByMegano_UpdateMyAvatar]
- @UserId int,
- @Text nvarchar(50),
- @Image varbinary(max) = NULL
- AS
- BEGIN
- UPDATE ByMegano_MyAvatarTable
- SET Text = @Text, Image = @Image
- WHERE UserId = @UserId
- END
- GO
Следует обратить внимание на то, что непосредственно перед исполнением DNN заменит метки {databaseOwner}{objectQualifier} соответствующими значениями.
По аналогии файл Uninstall.SqlDataProvider выполнит SQL код в момент удаления модуля:
- GO
- IF EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'{databaseOwner}[{objectQualifier}ByMegano_CreateMyAvatar]') AND type in (N'P', N'PC'))
- DROP PROCEDURE {databaseOwner}[{objectQualifier}ByMegano_CreateMyAvatar]
- GO
- IF EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'{databaseOwner}[{objectQualifier}ByMegano_DeleteMyAvatar]') AND type in (N'P', N'PC'))
- DROP PROCEDURE {databaseOwner}[{objectQualifier}ByMegano_DeleteMyAvatar]
- GO
- IF EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'{databaseOwner}[{objectQualifier}ByMegano_GetMyAvatarByUserId]') AND type in (N'P', N'PC'))
- DROP PROCEDURE {databaseOwner}[{objectQualifier}ByMegano_GetMyAvatarByUserId]
- GO
- IF EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'{databaseOwner}[{objectQualifier}ByMegano_UpdateMyAvatar]') AND type in (N'P', N'PC'))
- DROP PROCEDURE {databaseOwner}[{objectQualifier}ByMegano_UpdateMyAvatar]
- GO
- IF EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'{databaseOwner}[{objectQualifier}ByMegano_MyAvatarTable]') AND type in (N'U'))
- DROP TABLE {databaseOwner}[{objectQualifier}ByMegano_MyAvatarTable]
- GO
Последний файл - AvatarSample.dnn, это метафайл модуля, в нем описываются все файлы, из которых он состоит:
- <dotnetnuke version="3.0" type="Module">
- <folders>
- <folder>
- <name>Avatar</name>
- <friendlyname>Avatar</friendlyname>
- <foldername>Avatar</foldername>
- <modulename>Avatar</modulename>
- <description>A Avatar module</description>
- <version>01.00.00</version>
- <businesscontrollerclass>ByMegano.AvatarSample.Components.AvatarController</businesscontrollerclass>
- <modules>
- <module>
- <friendlyname>Avatar</friendlyname>
- <cachetime>0</cachetime>
- <controls>
- <control>
- <src>DesktopModules/Avatar/ViewAvatarSample.ascx</src>
- <type>View</type>
- <helpurl></helpurl>
- </control>
- <control>
- <key>Edit</key>
- <title>Edit Content</title>
- <src>DesktopModules/Avatar/EditAvatarSample.ascx</src>
- <type>Edit</type>
- <helpurl></helpurl>
- </control>
- </controls>
- </module>
- </modules>
- <files>
- <file>
- <name>ByMegano.AvatarSample.dll</name>
- </file>
- <file>
- <name>ViewAvatarSample.ascx</name>
- </file>
- <file>
- <name>EditAvatarSample.ascx</name>
- </file>
- <file>
- <name>01.00.00.SqlDataProvider</name>
- </file>
- <file>
- <name>Uninstall.SqlDataProvider</name>
- </file>
- <file>
- <name>noAvatar.jpg</name>
- </file>
- <file>
- <name>Image.aspx</name>
- </file>
- <file>
- <name>module.css</name>
- </file>
- </files>
- </folder>
- </folders>
- </dotnetnuke>
Как видите файлов всего 8, это файлы разметки, css, а так же библиотека сборки. Вы можете произвести Build проекта, собрать указанные файлы в zip архив и установить в DNN. Так же готовый к установке модуль можно найти в исходниках.
Заключение:
В данной статье мы рассмотрели создание простого модуля. Однако, из за большого количества материала, не было затронуто множество других тем, таких как, локализация, настройка модуля и прочее, но базовые знания, полученные здесь, помогут вам в дальнейшем сосредоточиться на достижении поставленной задачи, не теряясь в деталях.
Если вы чего-то не до поняли, или что то вышло не так, то всегда можете скачать файлы проектов и модуля прикрепленные к статье и сравнить их между собой, а так же, создать проект нового модуля и посмотреть его код.
Александр Кобелев aka Megano