public class Book { public string Title { get; set; } public int Id { get; set; } public Author Author { get; set; } public Publisher Publisher { get; set; } public IList<Book> GetBooks() { var books = new List<Book>(); for (int i = 0; i < 10; i++) { var book = new Book() { Id = i, Title = $"Title {i}", Author = new Author() { FirstName = $"Author {i} First Name", LastName = $"Author {i} Last Name", Person = new Person() { Type = $"Type {i}", Value = $"Value {i}" } }, Publisher = new Report.Publisher() { Name = $"Publiser {i}" } }; books.Add(book); } return books; } } public class Author { public string FirstName { get; set; } public string LastName { get; set; } public Person Person { get; set; } } public class Person { public string Type { get; set; } public string Value { get; set; } } public class Publisher { public string Name { get; set; } }
بررسی روش آپلود فایلها در ASP.NET Core
private async Task UploadFiles(InputFileChangeEventArgs e) { foreach (var file in e.GetMultipleFiles()) { var fileData = new FileToBeSaveVM(); var buffers = new byte[file.Size]; await file.OpenReadStream().ReadAsync(buffers); fileData.FileName = file.Name; fileData.FileSize = file.Size; fileData.FileType = file.ContentType; fileData.Extension = Path.GetExtension(file.Name); fileData.ImageBytes = buffers; lstFileToBeSaves.fileToBeSaves.Add(fileData); } } public class FileToBeSaveVM { public byte[] ImageBytes { get; set; } public string FileName { get; set; } public string FileType { get; set; } public string Extension { get; set; } public long FileSize { get; set; } }
public async Task UploadFileAsync(List<FileToBeSaveVM> files, string uploadFolder) { var folderDirectory=createUploadDir(uploadFolder); foreach (var file in files) { string fileExtenstion = Path.GetExtension(file.FileName); string fileuniqName =$"{Guid.NewGuid()}{fileExtenstion}"; string fileName = Path.Combine(folderDirectory, fileuniqName); string url=$"{uploadFolder}/{fileuniqName}"; using (var fileStream = File.Create(fileName)) { await fileStream.WriteAsync(file.ImageBytes); } }
معرفی System.Text.Json در NET Core 3.0.
namespace System.Text.Json { public static class JsonSerializer { public static object Deserialize(ReadOnlySpan<byte> utf8Json, Type returnType, JsonSerializerOptions options = null); public static object Deserialize(string json, Type returnType, JsonSerializerOptions options = null); public static TValue Deserialize<TValue>(ReadOnlySpan<byte> utf8Json, JsonSerializerOptions options = null); public static TValue Deserialize<TValue>(string json, JsonSerializerOptions options = null); public static object Deserialize(ref Utf8JsonReader reader, Type returnType, JsonSerializerOptions options = null); public static TValue Deserialize<TValue>(ref Utf8JsonReader reader, JsonSerializerOptions options = null); public static ValueTask<object> DeserializeAsync(Stream utf8Json, Type returnType, JsonSerializerOptions options = null, CancellationToken cancellationToken = default); public static ValueTask<TValue> DeserializeAsync<TValue>(Stream utf8Json, JsonSerializerOptions options = null, CancellationToken cancellationToken = default); public static string Serialize(object value, Type inputType, JsonSerializerOptions options = null); public static string Serialize<TValue>(TValue value, JsonSerializerOptions options = null); public static void Serialize(Utf8JsonWriter writer, object value, Type inputType, JsonSerializerOptions options = null); public static void Serialize<TValue>(Utf8JsonWriter writer, TValue value, JsonSerializerOptions options = null); public static Task SerializeAsync(Stream utf8Json, object value, Type inputType, JsonSerializerOptions options = null, CancellationToken cancellationToken = default); public static Task SerializeAsync<TValue>(Stream utf8Json, TValue value, JsonSerializerOptions options = null, CancellationToken cancellationToken = default); public static byte[] SerializeToUtf8Bytes(object value, Type inputType, JsonSerializerOptions options = null); public static byte[] SerializeToUtf8Bytes<TValue>(TValue value, JsonSerializerOptions options = null); } }
namespace System.Text.Json { public sealed class JsonSerializerOptions { public JsonSerializerOptions(); public bool AllowTrailingCommas { get; set; } public IList<JsonConverter> Converters { get; } public int DefaultBufferSize { get; set; } public JsonNamingPolicy DictionaryKeyPolicy { get; set; } public bool IgnoreNullValues { get; set; } public bool IgnoreReadOnlyProperties { get; set; } public int MaxDepth { get; set; } public bool PropertyNameCaseInsensitive { get; set; } public JsonNamingPolicy PropertyNamingPolicy { get; set; } public JsonCommentHandling ReadCommentHandling { get; set; } public bool WriteIndented { get; set; } public JsonConverter GetConverter(Type typeToConvert); } }
namespace System.Text.Json.Serialization { public abstract class JsonConverter { public abstract bool CanConvert(Type typeToConvert); } }
متدی تحت عنوان ValidateEmail را تصور کنید. این متد از حیث بازگشت نتیجه به عنوان خروجی میتواند به اشکال مختلفی پیاده سازی شود که در ادامه مشاهده میکنیم:
متد ValidateEmail با خروجی Boolean
public bool ValidateEmail(string email) { var valid = true; if (string.IsNullOrWhiteSpace(email)) { valid = false; } var isValidFormat = true;//todo: using RegularExpression if (!isValidFormat) { valid = false; } var isRealDoamin = true;//todo: Code here that confirms whether domain exists. if (!isRealDoamin) { valid = false; } return valid; }
همانطور که در تکه کد زیر مشخص میباشد، استفاده کننده از متد بالا، امکان بررسی خروجی آن را در قالب یک شرط خواهد داشت و علاوه بر اینکه پیاده سازی آن ساده میباشد، خوانایی کد را نیز بالا میبرد؛ ولی با این حال نمیتوان متوجه شد مشکل اصلی آدرس ایمیل ارسالی به عنوان آرگومان، دقیقا چیست.
var email = "email@example.com"; var isValid = ValidateEmail(email); if(isValid) { //do something }
متد ValidateEmail با صدور استثناء
public void ValidateEmail(string email) { if (string.IsNullOrWhiteSpace(email)) throw new ArgumentNullException(nameof(email)); var isValidFormat = true;//todo: using RegularExpression if (!isValidFormat) throw new ArgumentException("email is not in a correct format"); var isRealDoamin = true;//todo: Code here that confirms whether domain exists. if (!isRealDoamin) throw new ArgumentException("email does not include a valid domain.") }
روش بالا هم جواب میدهد ولی بهتر است کلاس Exception سفارشی به عنوان مثال ValidationException برای این قضیه در نظر گرفته شود تا بتوان وهلههای صادر شده از این نوع را در لایههای بالاتر مدیریت کرد.
متد ValidateEmail با چندین خروجی
برای این منظور چندین راه حل پیش رو داریم.
با استفاده از پارامتر out:
public bool ValidateEmail(string email, out string message) { var valid = true; message = string.Empty; if (string.IsNullOrWhiteSpace(email)) { valid = false; message = "email is null."; } if (valid) { var isValidFormat = true;//todo: using RegularExpression if (!isValidFormat) { valid = false; message = "email is not in a correct format"; } } if (valid) { var isRealDoamin = true;//todo: Code here that confirms whether domain exists. if (!isRealDoamin) { valid = false; message = "email does not include a valid domain."; } } return valid; }
var email = "email@example.com"; var isValid = ValidateEmail(email, out string message); if (isValid) { //do something }
Tuple<bool, List<string>> result = Tuple.Create<bool, List<string>>(true, new List<string>());
public class OperationResult { public bool Success { get; set; } public IList<string> Messages { get; } = new List<string>(); public void AddMessage(string message) { Messages.Add(message); } }
public OperationResult ValidateEmail(string email) { var result = new OperationResult(); if (string.IsNullOrWhiteSpace(email)) { result.Success = false; result.AddMessage("email is null."); } if (result.Success) { var isValidFormat = true;//todo: using RegularExpression if (!isValidFormat) { result.Success = false; result.AddMessage("email is not in a correct format"); } } if (result.Success) { var isRealDoamin = true;//todo: Code here that confirms whether domain exists. if (!isRealDoamin) { result.Success = false; result.AddMessage("email does not include a valid domain."); } } return result; }
این بار خروجی متد مذکور از نوع OperationResult ای میباشد که هم موفقیت آمیز بودن یا عدم آن را مشخص میکند و همچنین امکان دسترسی به لیست پیغامهای مرتبط با اعتبارسنجیهای انجام شده، وجود دارد.
استفاده از Exception برای نمایش پیغام برای کاربر نهایی
با صدور یک استثناء و مدیریت سراسری آن در بالاترین (خارجی ترین) لایه و نمایش پیغام مرتبط با آن به کاربر نهایی، میتوان از آن به عنوان ابزاری برای ارسال هر نوع پیغامی به کاربر نهایی استفاده کرد. اگر قوانین تجاری با موفقیت برآورده نشدهاند یا لازم است به هر دلیلی یک پیغام مرتبط با یک اعتبارسنجی تجاری را برای کاربر نمایش دهید، این روش بسیار کارساز میباشد و با یکبار وقت گذاشتن برای توسعه زیرساخت برای این موضوع به عنوان یک Cross Cutting Concern تحت عنوان Exception Management آزادی عمل زیادی در ادامه توسعه سیستم خود خواهید داشت.
به عنوان مثال داشتن یک کلاس Exception سفارشی تحت عنوان UserFriendlyException در این راستا یک الزام میباشد.
[Serializable] public class UserFriendlyException : Exception { public string Details { get; private set; } public int Code { get; set; } public UserFriendlyException() { } public UserFriendlyException(SerializationInfo serializationInfo, StreamingContext context) : base(serializationInfo, context) { } public UserFriendlyException(string message) : base(message) { } public UserFriendlyException(int code, string message) : this(message) { Code = code; } public UserFriendlyException(string message, string details) : this(message) { Details = details; } public UserFriendlyException(int code, string message, string details) : this(message, details) { Code = code; } public UserFriendlyException(string message, Exception innerException) : base(message, innerException) { } public UserFriendlyException(string message, string details, Exception innerException) : this(message, innerException) { Details = details; } }
و همچنین لازم است در بالاترین لایه سیستم خود به عنوان مثال برای یک پروژه ASP.NET MVC یا ASP.NET Core MVC میتوان یک ExceptionFilter سفارشی نیز تهیه کرد که هم به صورت سراسری استثناءهای سفارشی شما را مدیریت کند و همچنین خروجی مناسب Json برای استفاده در سمت کلاینت را نیز مهیا کند. به عنوان مثال برای درخواستهای Ajax ای لازم است در سمت کلاینت نیز پاسخهای رسیده از سمت سرور به صورت سراسری مدیریت شوند و برای سایر درخواستها همان نمایش صفحات خطای پیغام مرتبط با استثناء رخ داده شده کفایت میکند.
یک مدل پیشنهادی برای تهیه خروجی مناسب برای ارسال جزئیات استثنا رخ داده در درخواستهای Ajax ای
[Serializable] public class MvcAjaxResponse : MvcAjaxResponse<object> { public MvcAjaxResponse() { } public MvcAjaxResponse(bool success) : base(success) { } public MvcAjaxResponse(object result) : base(result) { } public MvcAjaxResponse(ErrorInfo error, bool unAuthorizedRequest = false) : base(error, unAuthorizedRequest) { } } [Serializable] public class MvcAjaxResponse<TResult> : MvcAjaxResponseBase { public MvcAjaxResponse(TResult result) { Result = result; Success = true; } public MvcAjaxResponse() { Success = true; } public MvcAjaxResponse(bool success) { Success = success; } public MvcAjaxResponse(ErrorInfo error, bool unAuthorizedRequest = false) { Error = error; UnAuthorizedRequest = unAuthorizedRequest; Success = false; } /// <summary> /// The actual result object of AJAX request. /// It is set if <see cref="MvcAjaxResponseBase.Success" /> is true. /// </summary> public TResult Result { get; set; } } public class MvcAjaxResponseBase { public string TargetUrl { get; set; } public bool Success { get; set; } public ErrorInfo Error { get; set; } public bool UnAuthorizedRequest { get; set; } public bool __mvc { get; } = true; }
[Serializable] public class ErrorInfo { public int Code { get; set; } public string Message { get; set; } public string Detail { get; set; } public Dictionary<string, string> ValidationErrors { get; set; } public ErrorInfo() { } public ErrorInfo(string message) { Message = message; } public ErrorInfo(int code) { Code = code; } public ErrorInfo(int code, string message) : this(message) { Code = code; } public ErrorInfo(string message, string details) : this(message) { Detail = details; } public ErrorInfo(int code, string message, string details) : this(message, details) { Code = code; } }
public async Task CheckIsDeactiveAsync(long id) { if (await _organizationalUnits.AnyAsync(a => a.Id == id && !a.IsActive).ConfigureAwait(false)) throw new UserFriendlyException("واحد سازمانی جاری غیرفعال میباشد."); }
روش نام گذاری متدهایی که امکان بازگشت خروجی Null را دارند
public User GetById(long id);
[Serializable] public class EntityNotFoundException : Exception { public Type EntityType { get; set; } public object Id { get; set; } public EntityNotFoundException() { } public EntityNotFoundException(string message) : base(message) { } public EntityNotFoundException(string message, Exception innerException) : base(message, innerException) { } public EntityNotFoundException(SerializationInfo serializationInfo, StreamingContext context) : base(serializationInfo, context) { } public EntityNotFoundException(Type entityType, object id) : this(entityType, id, null) { } public EntityNotFoundException(Type entityType, object id, Exception innerException) : base($"There is no such an entity. Entity type: {entityType.FullName}, id: {id}", innerException) { EntityType = entityType; Id = id; } }
یک مثال واقعی
public async Task<UserOrganizationalUnitInfo> GetCurrentOrganizationalUnitInfoOrNullAsync(long userId) { return (await _setting.GetSettingValueForUserAsync( UserSettingNames.CurrentOrganizationalUnitInfo, userId).ConfigureAwait(false)) .FromJsonString<UserOrganizationalUnitInfo>(); }
مدلهای برنامه
using System.Collections.Generic; namespace jQueryMvcSample05.Models { public class Survey { public int Id { set; get; } public string Title { set; get; } public virtual ICollection<SurveyItem> SurveyItems { set; get; } } }
namespace jQueryMvcSample05.Models { public class SurveyItem { public int Id { set; get; } public string Title { set; get; } public int Order { set; get; } //[ForeignKey("SurveyId")] public virtual Survey Survey { set; get; } public int SurveyId { set; get; } } }
تعدادی نظر سنجی به همراه گزینههای آنها تعریف خواهند شد (یک رابطه one-to-many است). سپس توسط افزونه sortable میخواهیم ترتیب قرارگیری گزینههای آنرا مشخص کنیم یا تغییر دهیم.
منبع داده فرضی برنامه
using System.Collections.Generic; using jQueryMvcSample05.Models; namespace jQueryMvcSample05.DataSource { /// <summary> /// یک منبع داده فرضی جهت دموی سادهتر برنامه /// </summary> public static class SurveysDataSource { private static IList<Survey> _surveysCache; static SurveysDataSource() { _surveysCache = createSurveys(); } public static IList<Survey> SystemSurveys { get { return _surveysCache; } } private static IList<Survey> createSurveys() { var results = new List<Survey>(); for (int i = 1; i < 6; i++) { results.Add(new Survey { Id = i, Title = "نظر سنجی " + i, SurveyItems = new List<SurveyItem> { new SurveyItem{ Id = 1, SurveyId = i, Title = "گزینه 1", Order = 1 }, new SurveyItem{ Id = 2, SurveyId = i, Title = "گزینه 2", Order = 2 }, new SurveyItem{ Id = 3, SurveyId = i, Title = "گزینه 3", Order = 3 }, new SurveyItem{ Id = 4, SurveyId = i, Title = "گزینه 4", Order = 4 } } }); } return results; } } }
کدهای کنترلر برنامه
using System.Collections.Generic; using System.Linq; using System.Web.Mvc; using System.Web.UI; using jQueryMvcSample05.DataSource; using jQueryMvcSample05.Security; namespace jQueryMvcSample05.Controllers { public class HomeController : Controller { [HttpGet] public ActionResult Index() { var surveysList = SurveysDataSource.SystemSurveys; return View(surveysList); } [HttpPost] [AjaxOnly] [OutputCache(Location = OutputCacheLocation.None, NoStore = true)] public ActionResult SortItems(int? surveyId, string[] items) { if (items == null || items.Length == 0 || surveyId == null) return Content("nok"); updateSurvey(surveyId, items); return Content("ok"); } /// <summary> /// این متد جهت آشنایی با پروسه به روز رسانی ترتیب گزینهها در اینجا قرار گرفته است /// بدیهی است محل قرارگیری آن باید در لایه سرویس برنامه اصلی باشد /// </summary> private static void updateSurvey(int? surveyId, string[] items) { var itemIds = new List<int>(); foreach (var item in items) { itemIds.Add(int.Parse(item.Replace("item-row-", string.Empty))); } var survey = SurveysDataSource.SystemSurveys.FirstOrDefault(x => x.Id == surveyId.Value); if (survey == null) return; int order = 0; foreach (var itemId in itemIds) { order++; var surveyItem = survey.SurveyItems.FirstOrDefault(x => x.Id == itemId); if (surveyItem == null) continue; surveyItem.Order = order; } //todo: call save changes .... } } }
و کدهای View متناظر
@model IList<jQueryMvcSample05.Models.Survey> @{ ViewBag.Title = "Index"; var sortUrl = Url.Action(actionName: "SortItems", controllerName: "Home"); } <h2> نظر سنجیها</h2> @foreach (var survey in Model) { <fieldset> <legend>@survey.Title</legend> <div id="sortable-@survey.Id"> @foreach (var surveyItem in survey.SurveyItems.OrderBy(x => x.Order)) { <div id="item-row-@surveyItem.Id"> <span class="handles">::</span> @surveyItem.Title </div> } </div> </fieldset> } <div> لطفا برای تغییر ترتیب آیتمهای تعریف شده، از امکان کشیدن و رها کردن تعریف شده بر روی آیکونهای :: در کنار هر آیتم استفاده نمائید. </div> @section JavaScript { <script type="text/javascript"> $(document).ready(function () { $('div[id^="sortable"]').sortable({ handle: 'span' }).bind('sortupdate', function (e, ui) { var sortableItemId = $(ui.item).parent().attr('id'); var surveyId = sortableItemId.replace('sortable-', ''); var items = []; $('#' + sortableItemId + ' div').each(function () { items.push($(this).attr('id')); }); //alert(items.join('&')); $.ajax({ type: "POST", url: "@sortUrl", data: JSON.stringify({ items: items, surveyId: surveyId }), contentType: "application/json; charset=utf-8", dataType: "json", complete: function (xhr, status) { var data = xhr.responseText; if (xhr.status == 403) { window.location = "/login"; } else if (status === 'error' || !data || data == "nok") { alert('خطایی رخ داده است'); } else { alert('انجام شد'); } } }); }); }); </script> }
در اینجا نیاز بود تا ابتدا کدهای کنترلر و View ارائه شوند، تا بتوان در مورد ارتباطات بین آنها بهتر بحث کرد.
در ابتدای نمایش صفحه Home، رکوردهای نظرسنجیها از منبع داده دریافت شده و به View ارسال میشوند. در View برنامه یک حلقه تشکیل گردیده و این موارد رندر خواهند شد.
هر نظر سنجی با یک div بیرونی که با id مساوی sortable شروع میشود، آغاز گردیده و گزینههای آن نظر سنجی نیز توسط divهایی با id مساوی item-row شروع خواهند گردید. هر کدام از این idها حاوی id رکوردهای متناظر هستند. از این idها در کدهای برنامه جهت یافتن یک نظر سنجی یا یک ردیف مشخص برای به روز رسانی ترتیب آنها استفاده خواهیم کرد.
ادامه کار، به تنظیمات و اعمال افزونه sortable مرتبط میشود. توسط تنظیم ذیل به jQuery اعلام خواهیم کرد، هرجایی یک div با id شروع شده با sortable یافتی، افزونه sortable را به آن متصل کن:
$('div[id^="sortable"]').sortable
var sortableItemId = $(ui.item).parent().attr('id'); var surveyId = sortableItemId.replace('sortable-', ''); var items = []; $('#' + sortableItemId + ' div').each(function () { items.push($(this).attr('id')); });
data: JSON.stringify({ items: items, surveyId: surveyId })
public ActionResult SortItems(int? surveyId, string[] items)
اطلاعاتی که در اینجا دریافت میشوند در متد updateSurvey مورد استفاده قرار خواهند گرفت. بر اساس surveyId دریافتی، نظرسنجی مرتبط را یافته و سپس به گزینههای آن دست خواهیم یافت. اکنون نوبت به پردازش آرایه items دریافت شده است. این آرایه بر اساس انتخاب کاربر مرتب شده است.
دریافت کدها و پروژه کامل این قسمت
jQueryMvcSample05.zip
public class CmsUser : IdentityUser { public string DisplayName { get; set; } }
public class Post { private IList<string> _tags = new List<string>(); public int id { get; set; } public string name { get; set; } public string slug { get; set; } public string description { get; set; } public DateTime? publishTime { get; set; } public string content { get; set; } public IList<string> tags { get { return _tags; } set { _tags = value; } } public string CombineTags { get { return string.Join(",", _tags); } set { _tags = value.Split(',').Select(x => x.Trim()).ToList(); } } public string AuthorID { get; set; } // [ForeignKey("AuthorID")] // public CmsUser Author { get; set; } }
public class Column<T>
{
public string Name { set; get; }
public T Data { set; get; }
}
مشکلی که با این نوع کلاسها وجود دارد این است که نمیتوان مثلا لیست زیر را در مورد آنها تعریف کرد:
IList<Column<T>> myList = new List<Column<T>>();
به عبارتی میخواهیم یک لیست از کلاسی جنریک داشته باشیم. راه حل انجام آن به صورت زیر است:
using System.Collections;
namespace Tests
{
public interface IColumn
{
string Name { set; get; }
object Data { set; get; }
}
public class Column<T> : IColumn
{
public string Name { set; get; }
public T Data { set; get; }
object IColumn.Data
{
get { return this.Data; }
set { this.Data = (T)value; }
}
}
}
ابتدا یک اینترفیس عمومی را همانند اعضای کلاس Column تعریف میکنیم که در آن بجای T از object استفاده شده است. سپس یک پیاده سازی جنریک از این اینترفیس را ارائه خواهیم داد؛ با این تفاوت که اینبار خاصیت Data مربوط به اینترفیس، به صورت خصوصی و صریح با استفاده از IColumn.Data تعریف میشود و نمونهی جنریک هم نام آن، عمومی خواهد بود.
اکنون میتوان نوشت:
var myList = new List<IColumn>();
برای مثال در این حالت تعریف لیست زیر که از تعدادی وهلهی کلاسی جنریک ایجاد شده، کاملا مجاز میباشد:
var myList = new List<IColumn>
{
new Column<int> { Data = 1, Name = "Col1"},
new Column<double> { Data = 1.2, Name = "Col2"}
};
خوب، تا اینجا یک مرحله پیشرفت است.اکنون اگر بخواهیم در این لیست، Data مثلا عنصری را که نامش Col1 است، دریافت کنیم چه باید کرد؟ آن هم نه به شکل object بلکه از نوع T مشخص:
static T GetColumnData<T>(IList<IColumn> list, string name)
{
var column = (Column<T>)Convert.ChangeType(list.Single(s => s.Name.Equals(name)), typeof(Column<T>), null);
return column.Data;
}
و نمونهای از استفاده آن:
int data = GetColumnData<int>(myList, "Col1");
این زیرنویسها فرمت ویژهای دارند:
<li class="transcript-module"> Introduction to ASP.NET MVC 4 <ul> <li class="transcript-clip" data-p="author=scott-allen&name=mvc4-building-m1-intro&mode=live&clip=0&course=mvc4-building"><a href="javascript:void(0)" onclick="LaunchPlayerWindow('http://pluralsight.com/training', 'author=scott-allen&name=mvc4-building-m1-intro&mode=live&clip=0&course=mvc4-building');">Introduction</a><br /> <div> <a href="javascript:void(0)" onclick="p(this);" data-s="1.636">Hi, this is Scott Allen and this is the first module in the course design</a> </div> </li> <li class="transcript-clip" data-p="author=scott-allen&name=mvc4-building-m1-intro&mode=live&clip=1&course=mvc4-building"><a href="javascript:void(0)" onclick="LaunchPlayerWindow('http://pluralsight.com/training', 'author=scott-allen&name=mvc4-building-m1-intro&mode=live&clip=1&course=mvc4-building');">Web Platform Installer</a><br /> <div> ...
public class TranscriptClip { public string Title { set; get; } public IList<TranscriptItem> TranscriptItems { set; get; } } public class TranscriptItem { public double StartTime { set; get; } public string Text { set; get; } }
برای استخراج این اطلاعات، یکی از بهترین ابزارها، کتابخانه HTML Agility pack است که توسط آن میتوان به liهای یاد شده دسترسی یافت:
var nodes = doc.DocumentNode.SelectNodes("//li[@class='transcript-clip']/div");
using System; using System.Collections.Generic; using System.Globalization; using System.IO; using System.Linq; using System.Text; using System.Text.RegularExpressions; using System.Web; using HtmlAgilityPack; namespace PluralsightTranscripts { public class TranscriptClip { public string Title { set; get; } public IList<TranscriptItem> TranscriptItems { set; get; } } public class TranscriptItem { public double StartTime { set; get; } public string Text { set; get; } } public class ExtractSubtitle { public static void ConvertToSrt(string fileName) { var transcriptClips = extractItems(fileName); var itemNumber = 1; foreach (var item in transcriptClips) { transcriptClipToSrt(item, itemNumber); itemNumber++; } } private static void transcriptClipToSrt(TranscriptClip item, int itemNumber) { var count = item.TranscriptItems.Count; var srtFileContent = transcriptItemsToSrt(item.TranscriptItems, count); var fileName = removeIllegalCharacters(string.Format("{0}-{1}.srt", itemNumber.ToString("00"), item.Title)); File.WriteAllText(fileName, srtFileContent); } private static string transcriptItemsToSrt(IList<TranscriptItem> items, int count) { var lineNumber = 1; var sb = new StringBuilder(); for (int row = 0; row < count; row++) { sb.AppendLine(lineNumber.ToString(CultureInfo.InvariantCulture)); sb.AppendLine(getTimeLine(items, count, row)); sb.AppendLine(items[row].Text); sb.AppendLine(string.Empty); lineNumber++; } return sb.ToString(); } private static string getTimeLine(IList<TranscriptItem> items, int count, int row) { var startTs = TimeSpan.FromSeconds(items[row].StartTime); var endTs = row + 1 < count ? TimeSpan.FromSeconds(items[row + 1].StartTime) : TimeSpan.FromSeconds(items[row].StartTime + 5); return string.Format("{0} --> {1}", timeSpanToString(startTs), timeSpanToString(endTs)); } private static string timeSpanToString(TimeSpan lineTs) { return string.Format("{0}:{1}:{2},{3}", lineTs.Hours.ToString("D2"), lineTs.Minutes.ToString("D2"), lineTs.Seconds.ToString("D2"), lineTs.Milliseconds.ToString("D3")); } private static string removeIllegalCharacters(string fileName) { string regexSearch = string.Format("{0}{1}", new string(Path.GetInvalidFileNameChars()), new string(Path.GetInvalidPathChars())); var r = new Regex(string.Format("[{0}]", Regex.Escape(regexSearch))); return r.Replace(fileName, "."); } private static IList<TranscriptClip> extractItems(string fileName) { var htmlContent = File.ReadAllText(fileName); var results = new List<TranscriptClip>(); var doc = new HtmlDocument { OptionCheckSyntax = true, OptionFixNestedTags = true, OptionAutoCloseOnEnd = true, OptionDefaultStreamEncoding = Encoding.UTF8 }; doc.LoadHtml(htmlContent); var nodes = doc.DocumentNode.SelectNodes("//li[@class='transcript-clip']/div"); foreach (var node in nodes) { var itemsList = new List<TranscriptItem>(); var title = node.ParentNode.ChildNodes.First(x => x.Name == "a").InnerText; foreach (var childNode in node.ChildNodes) { if (childNode.Name != "a") continue; var dataS = childNode.Attributes.First(x => x.Name == "data-s"); itemsList.Add(new TranscriptItem { StartTime = double.Parse(dataS.Value), Text = HttpUtility.HtmlDecode(childNode.InnerText.Trim()) }); } results.Add(new TranscriptClip { TranscriptItems = itemsList, Title = title }); } return results; } } }
فرمت SRT ساختار سادهای دارد. هر گفتگوی آن حداقل از سه سطر تشکیل میشود. سطر اول یک شماره خود افزاینده است. سطر دوم زمان شروع و پایان گفتگو را مشخص میکند و سطر سوم بیانگر متن گفتگو است. برای مثال:
1 00:00:01,636 --> 00:00:05,616 Hi, this is Scott Allen and this is the first module in the course design
دریافت پروژه کامل این مطلب
PluralsightTranscripts.zip
EF Code First #1
کدش از کتاب Code First که معرفی کردین استفاده کردم اما کد خودتون خطا نداره
using System; using System.Collections.Generic; namespace ChapterOneProject { public class Patient { public Patient() { Visits = new List<Visit>(); } public int Id { get; set; } public string Name { get; set; } public DateTime BirthDate { get; set; } //[ForeignKey("AnimalTypeId")] public AnimalType AnimalType { get; set; } //public int AnimalTypeId { get; set; } public DateTime FirstVisit { get; set; } public List<Visit> Visits { get; set; } } public class Visit { [Key] public int Id { get; set; } public DateTime Date { get; set; } public String ReasonForVisit { get; set; } public String Outcome { get; set; } public Decimal Weight { get; set; } //[ForeignKey("PatientId")] //public virtual Patient Patient { get; set; } public int PatientId { get; set; } } public class AnimalType { public int Id { get; set; } public string TypeName { get; set; } } }
کد کانتکست
public class VetContext : DbContext { public DbSet<Patient> Patients { get; set; } public DbSet<Visit> Visits { get; set; } //public DbSet<AnimalType> AnimalTypes { get; set; } }
var dog = new AnimalType { TypeName = "Dog" }; var visit = new List<Visit> { new Visit { Date = new DateTime(2011, 9, 1), Outcome = "Test", ReasonForVisit = "Test", Weight = 32, } }; var patient = new Patient { Name = "Sampson", BirthDate = new DateTime(2008, 1, 28), AnimalType = dog, Visits = visit, }; using (var context = new VetContext()) { context.Patients.Add(patient); context.SaveChanges(); }
کدهای دیگه تست کردم مشکلی نداشت اما این مورد ؟
با profiler چک کردم خطای عدم توانایی در تبدیل نوع datetime2 به datetime میده
اگر به گوگل ریدر دقت کرده باشید، دو گزینهی به اشتراک گذاری دارد: share و share with note .
اگر گزینهی share with note را انتخاب کرده و توضیحی را ارسال یا اضافه کنیم، این توضیحات، به فید از نوع Atom اشتراکها هم اضافه میشود. مثلا:
<?xml version="1.0"?>
<feed xmlns:media="http://search.yahoo.com/mrss/"
xmlns:gr="http://www.google.com/schemas/reader/atom/"
xmlns:idx="urn:atom-extension:indexing"
xmlns="http://www.w3.org/2005/Atom"
idx:index="no"
gr:dir="ltr">
...
<entry gr:crawl-timestamp-msec="1316627782108">
...
<gr:annotation>
<content type="html">text-text-text</content>
<author>
<name>Vahid</name>
</author>
</gr:annotation>
...
</entry>
...
</feed>
این افزونه استاندارد نیست و همانطور که در قسمت xmlns:gr اطلاعات فوق مشخص است، در فضای نام http://www.google.com/schemas/reader/atom/ معنا پیدا میکند. از دات نت سه و نیم به بعد هم کلاسی جهت خواندن فیدهای استاندارد وجود دارد (تعریف شده در فضای نام System.ServiceModel.Syndication). اما چگونه میتوان این افزونهی غیر استاندارد را با کمک امکانات توکار دات نت خواند؟
روش کار با استفاده از ElementExtensions هر آیتم یک فید است؛ به صورت زیر :
using System;
using System.Collections.Generic;
using System.Linq;
using System.ServiceModel.Syndication;
using System.Xml;
using System.Xml.Linq;
namespace Linq2Rss
{
public class RssEntry
{
public string Title { set; get; }
public string Description { set; get; }
public string Link { set; get; }
public DateTime PublicationDate { set; get; }
public string Author { set; get; }
public string BlogName { set; get; }
public string BlogAddress { set; get; }
public string Annotation { set; get; }
}
public static class AtomReader
{
private static string getAtomAnnotation(this SyndicationElementExtensionCollection items)
{
if (!items.Any()) return string.Empty;
var item = items.Where(x => x.OuterName.ToLowerInvariant() == "annotation").FirstOrDefault();
if (item == null) return string.Empty;
var element = item.GetObject<XElement>();
var content = element.Element("{http://www.w3.org/2005/Atom}content");
return content == null ? string.Empty : content.Value;
}
public static IList<RssEntry> GetEntries(string feedUrl)
{
using (var reader = XmlReader.Create(feedUrl))
{
var feed = SyndicationFeed.Load(reader);
if (feed == null) return null;
return feed.Items.Select(x =>
new RssEntry
{
Title = x.Title.Text,
Author = x.Authors.Any() ? x.Authors.First().Name : string.Empty,
Description = x.Content == null ? string.Empty : ((TextSyndicationContent)x.Content).Text,
Link = x.Links.Any() ? x.Links.First().Uri.AbsoluteUri : string.Empty,
PublicationDate = x.PublishDate.UtcDateTime,
BlogName = x.SourceFeed.Title.Text,
BlogAddress = x.SourceFeed.Links.Any() ? x.SourceFeed.Links.First().Uri.AbsoluteUri : string.Empty,
Annotation = x.ElementExtensions.getAtomAnnotation()
}).ToList();
}
}
}
}
در این مثال به کمک متد الحاقی getAtomAnnotation، مجموعهی SyndicationElementExtensionCollection هر آیتم یک فید بررسی شده، در بین اینها، موردی که از نوع annotation باشد انتخاب و سپس content آن استخراج میگردد.
نکتهای دیگر:
اکثر کلاسهای موجود در فضاهای نام مرتبط با XML در دات نت امکان خواندن اطلاعات را از یک Uri هم دارند؛ مانند مثال فوق و متد XmlReader.Create بکارگرفته شده در آن. اما اگر بخواهیم حین خواندن اطلاعات، یک پروکسی را نیز به پروسه جاری اضافه کنیم، به نظر خاصیت یا متدی جهت انجام اینکار وجود ندارد. برای رفع این مشکل میتوان یک پروکسی سراسری را تعریف کرد. تنها کافی است خاصیت System.Net.WebRequest.DefaultWebProxy مقدار دهی شود. پس از آن به صورت خودکار بر روی کل برنامه تاثیر خواهد گذاشت.