- PVSM.RU - https://www.pvsm.ru -
Цель урока. Научиться использовать файл конфигурации Web.config. Application section, создание своих ConfigSection и IConfig. Научиться загружать файлы, использование file-uploader для загрузки файла, последующая обработка файла.
В этом уроке мы рассмотрим работу с конфигурационным файлом Web.config. Это xml-файл и в нем хранятся настройки программы.
Рассмотрим подробнее, из чего состоит этот файл:
Аналогично Repository, конфигуратор будем создавать как сервис. Создаем IConfig и Config-реализацию в папке Global (/Global/Config/IConfig.cs):
public interface IConfig
{
string Lang { get; }
}
И
public class Config : IConfig
{
public string Lang
{
get
{
return "ru";
}
}
}
Добавляем строку в RegisterServices (/App_Start/NinjectWebCommon.cs):
kernel.Bind<IConfig>().To<Config>().InSingletonScope();
И выводим в BaseController:
[Inject]
public IConfig Config { get; set; }
Теперь сделаем в инициализации контроллера переопеределение CultureInfo в потоке (/Controllers/BaseController.cs):
protected override void Initialize(System.Web.Routing.RequestContext requestContext)
{
try
{
var cultureInfo = new CultureInfo(Config.Lang);
Thread.CurrentThread.CurrentCulture = cultureInfo;
Thread.CurrentThread.CurrentUICulture = cultureInfo;
}
catch (Exception ex)
{
logger.Error("Culture not found", ex);
}
base.Initialize(requestContext);
}
И добавим вывод даты в Index.cshtml (/Areas/Default/Views/Home/Index.cshtml):
@DateTime.Now.ToString("D")
Получаем вывод:
И по-настоящему свяжем это с Web.Config. Добавим в Web.config в appSettings строку:
<add key="Culture" value="ru" />
В Config.cs (/Global/Config/Config.cs):
public string Lang
{
get
{
return ConfigurationManager.AppSettings["Culture"] as string;
}
}
Запускаем – результат тот же, теперь изменим значение в Web.config на fr:
<add key="Culture" value="fr" />
Получаем дату:
mardi 5 mars 2013
Отлично! Можете попробовать еще с несколькими языками. Список сокращений находится тут http://msdn.microsoft.com/en-us/goglobal/bb896001.aspx [1]
В этой части мы рассмотрим создание своих собственных ConfigSection. В этой главе мы реализуем загрузку файлов и создание превью. Нам понадобятся следующие данные: во-первых, зависимость mime-type от расширения, и иконка файлов (для скачивания, например):
и во-вторых, данные для создания превью:
Оба типа делаются одинаково, так что я распишу только создание одного из них. Пусть это будет IconSize, для создания превью. Первое, что надо сделать — это создать класс, наследуемый ConfigurationElement (/Global/Config/IconSize.cs):
public class IconSize : ConfigurationElement
{
[ConfigurationProperty("name", IsRequired = true, IsKey = true)]
public string Name
{
get
{
return this["name"] as string;
}
}
[ConfigurationProperty("width", IsRequired = false, DefaultValue = "48")]
public int Width
{
get
{
return (int)this["width"];
}
}
[ConfigurationProperty("height", IsRequired = false, DefaultValue = "48")]
public int Height
{
get
{
return (int)this["height"];
}
}
}
Рассмотрим подробнее:
Следующий шаг – это создание класса коллекции (так как у нас будет множество элементов) и секции (/Global/Config/IconSize.cs):
public class IconSizesConfigSection : ConfigurationSection
{
[ConfigurationProperty("iconSizes")]
public IconSizesCollection IconSizes
{
get
{
return this["iconSizes"] as IconSizesCollection;
}
}
}
public class IconSizesCollection : ConfigurationElementCollection
{
protected override ConfigurationElement CreateNewElement()
{
return new IconSize();
}
protected override object GetElementKey(ConfigurationElement element)
{
return ((IconSize)element).Name;
}
}
В Web.config добавляем:
<iconConfig>
<iconSizes>
<add name="Avatar173Size" width="173" height="176" />
…
</iconSizes>
</iconConfig>
Теперь необходимо объявить класс разбора этой секции в configSection:
<section name="iconConfig" type="LessonProject.Global.Config.IconSizesConfigSection, LessonProject" />
Обратите внимание, что в описание type необходимо указать имя dll (LessonProject
), в которой он содержится. Это важно, но будет рассмотрено в unit-тестах.
Создадим одиночный конфиг для настроек по работе с smtp-почтой. Нам понадобятся:
Файл (/Global/Config/MailSetting.cs):
public class MailSetting : ConfigurationSection
{
[ConfigurationProperty("SmtpServer", IsRequired = true)]
public string SmtpServer
{
get
{
return this["SmtpServer"] as string;
}
set
{
this["SmtpServer"] = value;
}
}
[ConfigurationProperty("SmtpPort", IsRequired = false, DefaultValue="25")]
public int SmtpPort
{
get
{
return (int)this["SmtpPort"];
}
set
{
this["SmtpPort"] = value;
}
}
[ConfigurationProperty("SmtpUserName", IsRequired = true)]
public string SmtpUserName
{
get
{
return this["SmtpUserName"] as string;
}
set
{
this["SmtpUserName"] = value;
}
}
[ConfigurationProperty("SmtpPassword", IsRequired = true)]
public string SmtpPassword
{
get
{
return this["SmtpPassword"] as string;
}
set
{
this["SmtpPassword"] = value;
}
}
[ConfigurationProperty("SmtpReply", IsRequired = true)]
public string SmtpReply
{
get
{
return this["SmtpReply"] as string;
}
set
{
this["SmtpReply"] = value;
}
}
[ConfigurationProperty("SmtpUser", IsRequired = true)]
public string SmtpUser
{
get
{
return this["SmtpUser"] as string;
}
set
{
this["SmtpUser"] = value;
}
}
[ConfigurationProperty("EnableSsl", IsRequired = false, DefaultValue="false")]
public bool EnableSsl
{
get
{
return (bool)this["EnableSsl"];
}
set
{
this["EnableSsl"] = value;
}
}
}
Добавим в Web.config:
<section name="mailConfig" type="LessonProject.Global.Config.MailSetting, LessonProject" />
И
<mailConfig
SmtpServer="smtp.gmail.com"
SmtpPort="587"
SmtpUserName="lxndrpetrov"
SmtpPassword="**********"
SmtpReply="lxndrpetrov@gmail.com"
SmtpUser="test"
EnableSsl="true" />
Добавим все это теперь в IConfig.cs и Сonfig.cs (/Global/Config/IConfig.cs):
public interface IConfig
{
string Lang { get; }
IQueryable<IconSize> IconSizes { get; }
IQueryable<MimeType> MimeTypes { get; }
MailSetting MailSetting { get; }
}
И
public IQueryable<IconSize> IconSizes
{
get
{
IconSizesConfigSection configInfo = (IconSizesConfigSection)ConfigurationManager.GetSection("iconConfig");
return configInfo.IconSizes.OfType<IconSize>().AsQueryable<IconSize>();
}
}
public IQueryable<MimeType> MimeTypes
{
get
{
MimeTypesConfigSection configInfo = (MimeTypesConfigSection)ConfigurationManager.GetSection("mimeConfig");
return configInfo.MimeTypes.OfType<MimeType>().AsQueryable<MimeType>();
}
}
public MailSetting MailSetting
{
get
{
return (MailSetting)ConfigurationManager.GetSection("mailConfig");
}
}
Мы еще добавим MailTemplates — шаблоны которые нам понадобятся для рассылки email при регистрации, или при напоминании пароля.
Сейчас рассмотрим стандартный пример загрузки файла на сервер, и больше никогда не будем пользоваться таким способом. Класс SimpleFileView для взаимодействия (/Models/Info/SimpleFileView.cs):
public class SimpleFileView
{
public HttpPostedFileBase UploadedFile { get; set; }
}
Обратите внимание на наименование класса для приема файлов. Итак, создадим контроллер SimpleFileController (/Areas/Default/Controllers/SimpleFileController.cs):
public class SimpleFileController : DefaultController
{
[HttpGet]
public ActionResult Index()
{
return View(new SimpleFileView());
}
[HttpPost]
public ActionResult Index(SimpleFileView simpleFileView)
{
return View(simpleFileView);
}
}
И добавим View:
@model LessonProject.Models.Info.SimpleFileView
@{
ViewBag.Title = "Index";
Layout = "~/Areas/Default/Views/Shared/_Layout.cshtml";
}
<h2>Index</h2>
@using (Html.BeginForm("Index", "SimpleFile", FormMethod.Post, new {enctype = "multipart/form-data", @class = "form-horizontal" }))
{
<fieldset>
<div class="control-group">
<label class="control-label" for="Email">
Загрузите файл:</label>
<div class="controls">
@Html.TextBox("UploadedFile", Model.UploadedFile, new { type = "file", @class = "input-xlarge" })
@Html.ValidationMessage("UploadedFile")
</div>
</div>
<div class="form-actions">
<button type="submit" class="btn btn-primary">
Upload</button>
</div>
</fieldset>
}
Обратите внимание, на enctype в атрибутах формы и на type в атрибутах TextBox (на самом деле тип еще бывает password, checkbox, radio, но для них есть соответствующие методы в @Html-классе). Enctype необходимо установить в “multipart/form-data”, чтоб была возможность загрузить большой объём информации.
Загружаем и проверяем. Наш файл благополучно загружен, только необходимо сохранить InputStream в некий файл. Но оставим пока так и рассмотрим недостатки.
Первый недостаток – это то, что во всех браузерах форма выбора файла выглядит по-разному:
Конечно, ведь дизайнер представляет себе, что загрузка файлов выполняется как в Safari, а заказчик проверяет в Chrome и IE, и начинает спрашивать у разработчиков: «Что за самодеятельность?»
Второй недостаток –если форма не прошла валидацию, то эти поля необходимо выбрать заново. Т.е. есть такая форма:
И вдруг вы набрали пароль неверно, или капчу не так ввели, или фотография второго разворота паспорта слишком большая, или вы забыли перегнать из raw-формата в jpeg.
В итоге фотографии, прописку и капчу надо вводить заново. Естественно, это совсем не user friendly, и раздражает заказчика (к тому же дизайнер нарисовал красиво, а выглядит убого).
Определим как должна вести себя загрузка файла:
Это называется ajax-загрузка и для нее используем fineuploader (http://fineuploader.com/ [2]). Библиотека платная, но мы скачаем и соберем исходники (у нас же есть bundle!). Скачиваем исходники по ссылке: https://github.com/valums/file-uploader [3]. Перемещаем js-файлы в папку /Scripts/fine-uploader. Css-файлы перемещаем в /Content и изображения в /Content/images. Перепишем правильно url в fineuploader.css для изображений:
.qq-upload-spinner {
display: inline-block;
background: url("images/loading.gif");
width: 15px;
height: 15px;
vertical-align: text-bottom;
}
.qq-drop-processing {
display: none;
}
.qq-drop-processing-spinner {
display: inline-block;
background: url("images/processing.gif");
width: 24px;
height: 24px;
vertical-align: text-bottom;
}
Файлы инициализируем в BundleConfig.cs (/App_Start/BundleConfig.cs):
bundles.Add(new ScriptBundle("~/bundles/fineuploader")
.Include("~/Scripts/fine-uploader/header.js")
.Include("~/Scripts/fine-uploader/util.js")
.Include("~/Scripts/fine-uploader/button.js")
.Include("~/Scripts/fine-uploader/ajax.requester.js")
.Include("~/Scripts/fine-uploader/deletefile.ajax.requester.js")
.Include("~/Scripts/fine-uploader/handler.base.js")
.Include("~/Scripts/fine-uploader/window.receive.message.js")
.Include("~/Scripts/fine-uploader/handler.form.js")
.Include("~/Scripts/fine-uploader/handler.xhr.js")
.Include("~/Scripts/fine-uploader/uploader.basic.js")
.Include("~/Scripts/fine-uploader/dnd.js")
.Include("~/Scripts/fine-uploader/uploader.js")
.Include("~/Scripts/fine-uploader/jquery-plugin.js")
);
bundles.Add(new StyleBundle("~/Content/css/fineuploader")
.Include("~/Content/fineuploader.css"));
Создаем контроллер FileController.cs (/Areas/Default/Controllers/FileController.cs):
public class FileController : DefaultController
{
[HttpGet]
public ActionResult Index()
{
return View();
}
public ActionResult Upload(HttpPostedFileWrapper qqfile)
{
return Json(new { result = "ok", success = true});
}
}
Метод-action Upload принимает строковое значение qqfile, я ниже рассмотрю, почему так. А сейчас создадим View для Index. Для этого:
View для Index:
@{
ViewBag.Title = "Index";
Layout = "~/Areas/Default/Views/Shared/_Layout.cshtml";
}
@section styles {
@Styles.Render("~/Content/css/fineuploader")
}
@section scripts {
@Scripts.Render("~/bundles/fineuploader")
@Scripts.Render("~/Scripts/default/file-index.js")
}
<h2>Index</h2>
<fieldset>
<div class="control-group">
<label class="control-label" for="Text">
Image
</label>
<div class="controls">
<div id="UploadImage">
Upload
</div>
</div>
</div>
<div>
<img src="" alt="" id="ImagePreview" />
</div>
</fieldset>
Наша кнопка с id=UploadImage. Добавляем file-index.js файл для обработки (/Scripts/default/file-index.js):
function FileIndex() {
_this = this;
this.ajaxFileUpload = "/File/Upload";
this.init = function () {
$('#UploadImage').fineUploader({
request: {
endpoint: _this.ajaxFileUpload
},
}).on('error', function (event, id, name, reason) {
//do something
})
.on('complete', function (event, id, name, responseJSON) {
alert(responseJSON);
});
}
}
var fileIndex = null;
$().ready(function () {
fileIndex = new FileIndex();
fileIndex.init();
});
Теперь обработаем загрузку:
public ActionResult Upload(HttpPostedFileWrapper qqfile)
{
var extension = Path.GetExtension(qqfile.FileName);
if (!string.IsNullOrWhiteSpace(extension))
{
var mimeType = Config.MimeTypes.FirstOrDefault(p => string.Compare(p.Extension, extension, 0) == 0);
//если изображение
if (mimeType.Name.Contains("image"))
{
//тут сохраняем в файл
var filePath = Path.Combine("/Content/files", qqfile.FileName);
qqfile.SaveAs(Server.MapPath(filePath));
return Json(new
{
success = true,
result = "error",
data = new
{
filePath
}
});
}
}
return Json(new { error = "Нужно загрузить изображение", success = false });
}
В Content добавим папку files — это будет папка пользовательских данных. Разберем код:
Проверяем, всё ли загружается, и приступим к созданию превью.
Во-первых, мы немного схитрили с mime-type = «image...», ведь к ним относится и bmp, и tiff файлы, которые не поддерживаются браузерами.
Так что создадим класс PreviewCreator в проекте LessonProject.Tools (PreviewCreator.cs):
public static class PreviewCreator
{
public static bool SupportMimeType(string mimeType)
{
switch (mimeType)
{
case "image/jpg":
case "image/jpeg":
case "image/png":
case "image/gif":
return true;
}
return false;
}
}
И заменим в FileController.cs (/Areas/Default/Controller/FileController.cs):
if (mimeType != null && PreviewCreator.SupportMimeType(mimeType.Name))
В PreviewCreator есть много функций для создания превью, так что я перечислю разные варианты создания изображения и подробно разберу один из них. Стоит учесть, что все превью создаются в формате jpeg. Итак, какие есть варианты:
CreateAndSavePreview
) Если исходное изображение меньше, чем размеры превью, то изображение размещается посередине белого холста. Если по отношению к размерам исходный размер имеет вертикальную ориентированность (квадратик из портретного формата) – вырезаем верхнюю часть. Если же отношение горизонтально ориентированно относительно размера, то вырезаем середину.CreateAndSaveAvatar
) Если исходное изображение меньше, чем размеры превью, то изображение просто сохраняется. Если по отношению к размерам исходный размер имеет вертикальную ориентированность (квадратик из портретного формата) – то уменьшаем, по высоте. Если же отношение горизонтально ориентированно относительно размера, то вырезаем середину.CreateAndSaveImage
) Если изображение меньше, чем максимальные размеры, то сохраняем исходное. Если же изображение не вписывается в границы, то уменьшаем, чтобы оно не превышало максимальный размер, и сохраняем.CreateAndSaveFitToSize
) Если изображение меньше, чем размеры, то оно будет растянуто до необходимых размеров. С потерей качества, конечно же.CropAndSaveImage
) Кроме стандартных параметров передаются координаты для обрезки изображения. Cоздадим превью (CreateAndSavePreview
), взяв из конфигурации размеры для создания превью AvatarSize (/Areas/Default/Controllers/FileController.cs):
var filePreviewPath = Path.Combine("/Content/files/previews", qqfile.FileName);
var previewIconSize = Config.IconSizes.FirstOrDefault(c => c.Name == "AvatarSize");
if (previewIconSize != null)
{
PreviewCreator.CreateAndSavePreview(qqfile.InputStream, new Size(previewIconSize.Width, previewIconSize.Height), Server.MapPath(filePreviewPath));
}
return Json(new
{
success = true,
result = "error",
data = new
{
filePath,
filePreviewPath
}
});
Запускаем. Загружаем. Файлы должны загрузиться, и создастся превью.
Теперь сделаем обработку в file-index.js (/Scripts/default/file-index.js):
$('#UploadImage').fineUploader({
request: {
endpoint: _this.ajaxFileUpload
},
})
.on('error', function (event, id, name, reason) {
//do something
})
.on('complete', function (event, id, name, responseJSON) {
$("#ImagePreview").attr("src", responseJSON.data.filePreviewPath);
});
теперь наш файл загружается вместе с превью. Путь большого файла также можно передавать отдельно, и записывать, например, в hidden поле и сохранять в дальнейшем в БД как строку.
Что плохого в такой конструкции, так это две следующие проблемы:
Есть еще один метод загрузки файла. Файл свободно болтается в интернете, а мы указываем путь к нему (например, при авторизации с facebook), а мы уже по ссылке сохраняем этот файл.
Это делается так:
var webClient = new WebClient();
var bytes = webClient.DownloadData(url);
var ms = new MemoryStream(bytes);
Где url – путь к файлу. Можно сложнее, с использованием HttpWebRequest:
public ActionResult Export(string uri)
{
HttpWebRequest webRequest = (HttpWebRequest)WebRequest.Create(uri);
webRequest.Method = "GET";
webRequest.KeepAlive = false;
webRequest.PreAuthenticate = false;
webRequest.Timeout = 1000;
var response = webRequest.GetResponse();
var stream = response.GetResponseStream();
var previewIconSize = Config.IconSizes.FirstOrDefault(c => c.Name == "AvatarSize");
var filePreviewPath = Path.Combine("/Content/files/previews", Guid.NewGuid().ToString("N") + ".jpg");
if (previewIconSize != null)
{
PreviewCreator.CreateAndSavePreview(stream, new Size(previewIconSize.Width, previewIconSize.Height), Server.MapPath(filePreviewPath));
}
return Content("OK");
}
Тут файл задается через генерацию Guid.NewGuid. Проверяем:
http://localhost/File/Export?uri=https://st.free-lance.ru/users/chernikov/upload/sm_f_81850beffd0d0c89.jpg
Файл загрузился и обработан. Всё супер!
Рекомендую пройтись дебаггером по работе PreviewCreator, чтобы понять, как там всё устроено.
Все исходники находятся по адресу https://bitbucket.org/chernikov/lessons [4]
Автор: chernikov
Источник [5]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/asp-net-mvc/31664
Ссылки в тексте:
[1] http://msdn.microsoft.com/en-us/goglobal/bb896001.aspx: http://msdn.microsoft.com/en-us/goglobal/bb896001.aspx
[2] http://fineuploader.com/: http://fineuploader.com/
[3] https://github.com/valums/file-uploader: https://github.com/valums/file-uploader
[4] https://bitbucket.org/chernikov/lessons: https://bitbucket.org/chernikov/lessons
[5] Источник: http://habrahabr.ru/post/176069/
Нажмите здесь для печати.