var excel = new ExcelQueryFactory(pathToExcelFile); string sheetName = "Sheet1"; var persons = from a in excel.WorksheetNoHeader(sheetName) where a[1] == "Mashhad" //مقدار در ستون دوم select a; foreach (var a in persons) { for(int i=0;i<a.Count;i++) MessageBox.Show(a[i]); }
EF Code First #7
public class Order { public int OrderId { get; set; } public virtual Quotation Quotation { get; set; } } public class Quotation { public int QuotationId { get; set; } public virtual Order Order { get; set; } }
یکی از قابلیتهای جالب NHibernate امکان تعریف فیلدها به صورت پویا هستند. به این معنا که زیرساخت طراحی یک برنامه "فرم ساز" هم اکنون در اختیار شما است! سیستمی که امکان افزودن فیلدهای سفارشی را دارا است که توسط برنامه نویس در زمان طراحی اولیه آن ایجاد نشدهاند. در ادامه نحوهی تعریف و استفاده از این قابلیت را توسط Fluent NHibernate بررسی خواهیم کرد.
در اینجا کلاسی که قرار است توانایی افزودن فیلدهای سفارشی را داشته باشد به صورت زیر تعریف میشود:
using System.Collections;
namespace TestModel
{
public class DynamicEntity
{
public virtual int Id { get; set; }
public virtual IDictionary Attributes { set; get; }
}
}
Attributes در عمل همان فیلدهای سفارشی مورد نظر خواهند بود. جهت معرفی صحیح این قابلیت نیاز است تا نگاشت آنرا از نوع dynamic component تعریف کنیم:
using FluentNHibernate.Automapping;
using FluentNHibernate.Automapping.Alterations;
namespace TestModel
{
public class DynamicEntityMapping : IAutoMappingOverride<DynamicEntity>
{
public void Override(AutoMapping<DynamicEntity> mapping)
{
mapping.Table("tblDynamicEntity");
mapping.Id(x => x.Id);
mapping.IgnoreProperty(x => x.Attributes);
mapping.DynamicComponent(x => x.Attributes,
c =>
{
c.Map(x => (string)x["field1"]);
c.Map(x => (string)x["field2"]).Length(300);
c.Map(x => (int)x["field3"]);
c.Map(x => (double)x["field4"]);
});
}
}
}
ابتدا از IgnoreProperty جهت ندید گرفتن Attributes استفاده کردیم. زیرا درغیراینصورت در حالت Auto mapping ، یک رابطه چند به یک به علت وجود IDictionary به صورت خودکار ایجاد خواهد شد که نیازی به آن نیست (یافتن این نکته نصف روز کار برد ....! چون مرتبا خطای An association from the table DynamicEntity refers to an unmapped class: System.Collections.Idictionary ظاهر میشد و مشخص نبود که مشکل از کجاست).
سپس Attributes به عنوان یک DynamicComponent معرفی شده است. در اینجا چهار فیلد سفارشی را اضافه کردهایم. به این معنا که اگر نیاز باشد تا فیلد سفارشی دیگری به سیستم اضافه شود باید یکبار Session factory ساخته شود و SchemaUpdate فراخوانی گردد و خوشبختانه با وجود Fluent NHibernate ، تمام این تغییرات در کدهای برنامه قابل انجام است (بدون نیاز به سر و کار داشتن با فایلهای XML نگاشتها و ویرایش دستی آنها). از تغییر نام جدول که برای مثال در اینجا tblDynamicEntity در نظر گرفته شده تا افزودن فیلدهای دیگر در قسمت DynamicComponent فوق.
همچنین باتوجه به اینکه این نوع تغییرات (ساخت دوبار سشن فکتوری) مواردی نیستند که قرار باشد هر ساعت انجام شوند، بنابراین سربار آنچنانی را به سیستم تحمیل نمیکنند.
drop table tblDynamicEntity
create table tblDynamicEntity (
Id INT IDENTITY NOT NULL,
field1 NVARCHAR(255) null,
field2 NVARCHAR(300) null,
field3 INT null,
field4 FLOAT null,
primary key (Id)
)
اگر با SchemaExport، اسکریپت خروجی معادل با نگاشت فوق را تهیه کنیم به جدول فوق خواهیم رسید. نوع و طول این فیلدهای سفارشی بر اساس نوعی که برای اشیاء دیکشنری مشخص میکنید، تعیین خواهند شد.
چند مثال جهت کار با این فیلدهای سفارشی یا پویا :
نحوهی افزودن رکوردهای جدید بر اساس خاصیتهای سفارشی:
//insert
object savedId = 0;
using (var session = sessionFactory.OpenSession())
{
using (var tx = session.BeginTransaction())
{
var obj = new DynamicEntity();
obj.Attributes = new Hashtable();
obj.Attributes["field1"] = "test1";
obj.Attributes["field2"] = "test2";
obj.Attributes["field3"] = 1;
obj.Attributes["field4"] = 1.1;
savedId = session.Save(obj);
tx.Commit();
}
}
با خروجی
INSERT
INTO
tblDynamicEntity
(field1, field2, field3, field4)
VALUES
(@p0, @p1, @p2, @p3);
@p0 = 'test1' [Type: String (0)], @p1 = 'test2' [Type: String (0)], @p2 = 1
[Type: Int32 (0)], @p3 = 1.1 [Type: Double (0)]
نحوهی کوئری گرفتن از این اطلاعات (فعلا پایدارترین روشی را که برای آن یافتهام استفاده از HQL میباشد ...):
//query
using (var session = sessionFactory.OpenSession())
{
using (var tx = session.BeginTransaction())
{
//using HQL
var list = session
.CreateQuery("from DynamicEntity d where d.Attributes.field1=:p0")
.SetString("p0", "test1")
.List<DynamicEntity>();
if (list != null && list.Any())
{
Console.WriteLine(list[0].Attributes["field2"]);
}
tx.Commit();
}
}
select
dynamicent0_.Id as Id1_,
dynamicent0_.field1 as field2_1_,
dynamicent0_.field2 as field3_1_,
dynamicent0_.field3 as field4_1_,
dynamicent0_.field4 as field5_1_
from
tblDynamicEntity dynamicent0_
where
dynamicent0_.field1=@p0;
@p0 = 'test1' [Type: String (0)]
استفاده از HQL هم یک مزیت مهم دارد: چون به صورت رشته قابل تعریف است، به سادگی میتوان آنرا داخل دیتابیس ذخیره کرد. برای مثال یک سیستم گزارش ساز پویا هم در این کنار طراحی کرد ....
نحوهی به روز رسانی و حذف اطلاعات بر اساس فیلدهای پویا:
//update
using (var session = sessionFactory.OpenSession())
{
using (var tx = session.BeginTransaction())
{
var entity = session.Get<DynamicEntity>(savedId);
if (entity != null)
{
entity.Attributes["field2"] = "new-val";
tx.Commit(); // Persist modification
}
}
}
//delete
using (var session = sessionFactory.OpenSession())
{
using (var tx = session.BeginTransaction())
{
var entity = session.Get<DynamicEntity>(savedId);
if (entity != null)
{
session.Delete(entity);
tx.Commit();
}
}
}
PM> Install-Package LightInject
PM> Install-Package LightInject.Source
public class PersonModel { public int Id { get; set; } public string Name { get; set; } public string Family { get; set; } public DateTime Birth { get; set; } } public interface IRepository<T> where T:class { void Insert(T entity); IEnumerable<T> FindAll(); } public interface IPersonRepository:IRepository<PersonModel> { } public class PersonRepository:IPersonRepository { public void Insert(PersonModel entity) { throw new NotImplementedException(); } public IEnumerable<PersonModel> FindAll() { throw new NotImplementedException(); } } public interface IPersonService { void Insert(PersonModel entity); IEnumerable<PersonModel> FindAll(); } public class PersonService:IPersonService { private readonly IPersonRepository _personRepository; public PersonService(IPersonRepository personRepository) { _personRepository = personRepository; } public void Insert(PersonModel entity) { _personRepository.Insert(entity); } public IEnumerable<PersonModel> FindAll() { return _personRepository.FindAll(); } }
public partial class Form1 : Form { private readonly IPersonService _personService; public Form1(IPersonService personService) { _personService = personService; InitializeComponent(); } }
static void Main() { Application.EnableVisualStyles(); Application.SetCompatibleTextRenderingDefault(false); var container = new ServiceContainer(); container.Register<IPersonService, PersonService>(); container.Register<IPersonRepository, PersonRepository>(); Application.Run(new Form1(container.GetInstance<IPersonService>())); }
public class WorkerModel:PersonModel { public ManagerModel Manager { get; set; } } public class ManagerModel:PersonModel { public IEnumerable<WorkerModel> Workers { get; set; } } public class WorkerRepository:IPersonRepository { public void Insert(PersonModel entity) { throw new NotImplementedException(); } public IEnumerable<PersonModel> FindAll() { throw new NotImplementedException(); } } public class ManagerRepository:IPersonRepository { public void Insert(PersonModel entity) { throw new NotImplementedException(); } public IEnumerable<PersonModel> FindAll() { throw new NotImplementedException(); } } public class WorkerService:IPersonService { private readonly IPersonRepository _personRepository; public WorkerService(IPersonRepository personRepository) { _personRepository = personRepository; } public void Insert(PersonModel entity) { var worker = entity as WorkerModel; _personRepository.Insert(worker); } public IEnumerable<PersonModel> FindAll() { return _personRepository.FindAll(); } } public class ManagerService:IPersonService { private readonly IPersonRepository _personRepository; public ManagerService(IPersonRepository personRepository) { _personRepository = personRepository; } public void Insert(PersonModel entity) { var manager = entity as ManagerModel; _personRepository.Insert(manager); } public IEnumerable<PersonModel> FindAll() { return _personRepository.FindAll(); } }
... var container = new ServiceContainer(); container.Register<IPersonService, PersonService>(); container.Register<IPersonService, WorkerService>(); container.Register<IPersonRepository, PersonRepository>(); container.Register<IPersonRepository, WorkerRepository>(); Application.Run(new Form1(container.GetInstance<IPersonService>()));
... container.Register<IPersonService, PersonService>("PersonService"); container.Register<IPersonService, WorkerService>(); container.Register<IPersonRepository, PersonRepository>(); container.Register<IPersonRepository, WorkerRepository>(); Application.Run(new Form1(container.GetInstance<IPersonService>("PersonService")));
container.Register<IPersonService, PersonService>("PersonService"); Application.Run(new Form1(container.GetInstance<IPersonService>()));
container.Register<IPersonService, PersonService>(); container.Register<IPersonService, WorkerService>("WorkerService"); var personList = container.GetInstance<IEnumerable<IPersonService>>();
container.Register<IPersonService, PersonService>(); container.Register<IPersonService, WorkerService>("WorkerService"); var personList = container.GetAllInstances<IPersonService>();
- Array
- ICollection<T>
- IList<T>
- IReadOnlyCollection<T>
- IReadOnlyList<T>
container.RegisterInstance<string>("SomeValue"); var value = container.GetInstance<string>();
container.RegisterInstance<string>("SomeValue","String1"); container.RegisterInstance<string>("OtherValue","String2"); var value = container.GetInstance<string>("String2");
مفاهیم کلیدی
Case مهمترین مفهومی است که در تحلیل یک مسئله داده کاوی میبایست شناسائی شود و تشخیص اشتباه در شناسائی آن منجر به عدم موفقیت پروژه داده کاوی خواهد شد. Case به معنای یک موجودیت پایه از اطلاعات میباشد که عملیات داده کاوی بر روی آن انجام میشود و هدف از معرفی آن، معرفی ساختار مسئله به موتور داده کاوی است. هر Case شامل مجموعه ای از ویژگیها (Attributes) میباشد؛ مانند سن، جنسیت. ویژگیها میتوانند دارای یک مجموعه از مقادیر ممکن باشند که به آنها وضعیت یا مقدار (State/Value) میگویند؛ مانند جنسیت که دارای دو وضعیت زن یا مرد میباشد.
Case میتواند ساده باشد؛ برای نمونه زمانیکه قصد دارید «از اطلاعات آماری مشتریان به منظور تحلیل ریسک وام گرفتن» استفاده کنید، بدین ترتیب هر Case شامل اطلاعات یک مشتری و یا ردیفی از داده مشتریان است.
Case میتواند کمی پیچیدهتر باشد؛ برای مثال زمانیکه میخواهید «رفتار خرید مشتری را بر اساس تاریخچه خرید مشتری» تحلیل کنید، که در این صورت هر Case شامل یک رکورد از اطلاعات مشتری به همراه لیستی از محصولاتی که خریداری کرده است، میباشد. (توجه کنید تعریف رفتار به طور ضمنی، بیانگر عملکرد در طول زمان میباشد)
Case مثال فوق نمونه ای از Nested Case است، که به اطلاعات Details در ساختار Master/Details اشاره دارد. چنانچه Case ای از نوع Nested باشد، الگوریتمها به Case ای به عنوان ورودی فرمت مجموعه ردیف سلسله مراتبی (Hierarchical Row-set) نیاز دارند.
Case Key مشخصه ای است که یکتا بودن هر Case را مشخص میکند و اغلب Primary Key یک جدول رابطه ای است، همچنین ممکن است یک کلید ترکیبی باشد. ذکر این نکته ضروری است که بدانیم Case Key فقط یک شناسه است و شامل هیچ الگویی نمیباشد و بدین ترتیب غالباً بوسیله الگوریتمهای داده کاوی نادیده گرفته میشود.
Nested Key مهمترین مشخصه ویژگی از بخش Nested هر Case است و در واقع کلید معنایی تحلیل میباشد که شامل اطلاعات مفیدی دربارهی الگوهاست. به بیان دیگر ویژگی است که عناصر مختلف موجود در Nested Case را به ازای هر Case تفکیک میکند. همچنین در نظر داشته باشید که Nested Key یک شناسه نیست و دارای مفهومی متفاوت با Foreign Key است، بدین ترتیب سایر مشخصههای دیگر در بخش Nested؛ جهت توصیف Nested Key بکار میروند. برای نمونه چنانچه مدلی برای یادگیری الگوهایی درباره رفتار خرید مشتری داشته باشیم، Nested Key برابر با محصول و میزان خرید است.
به همین ترتیب Case Table جدولی است شامل اطلاعات Case و بطور مشابه Nested Table جدولی است که شامل اطلاعات مرتبط با قسمت Nested از Case میباشد. از اپراتور Shape به منظور پیوند میان Case Table و Nested Table استفاده میشود.
در خصوص Attribute ها (ویژگی ها) از آنجا که هر ویژگی؛ توصیف کننده مسئله داده کاوی از یک منظر خاص میباشد، میتوان اینگونه بیان نمود که هر چه تعداد ویژگیها در یک پروژه بیشتر
باشد، توان تحلیل در آن پروژه افزایش مییابد. انواع ویژگیها به دو دسته Discrete (گسسته) و Continuous (پیوسته) تقسیم میشوند. برای نمونه ویژگی جنسیت، تحصیلات و ... گسسته و همچنین ویژگی سن، درآمد و ... پیوسته هستند. به مقادیر موجود در یک ویژگی پیوسته Value و بطور مشابه به وضعیتهای موجود در یک ویژگی گسسته State گفته میشود. ویژگیها در یک الگوریتم از حیث کاربرد (Attribute Usage) به دو دسته Input و Output تقسیم میشوند.
یک الگوریتم از ویژگیهای ورودی (Input) استفاده میکند تا الگویی برای پیش بینی ویژگیهای خروجی (Output) پیدا کند. همچنین لازم است در نظر داشته باشید که برخی الگوریتمها نظیر Naïve Bayes صرفاً با دادههای گسسته و بطور مشابه الگوریتم هایی نظیر Logistic Regression تنها با مقادیر پیوسته کار میکنند.
مدل برنامه
در ابتدای کار نیاز است تا ساختاری را جهت ارائه لیستی از مطالب که دارای گزینه امتیاز دهی میباشند، تهیه کنیم:
namespace jQueryMvcSample03.Models { public class BlogPost { public int Id { set; get; } public string Title { set; get; } public string Body { set; get; } /// <summary> /// اطلاعات رای گیری یک مطلب به صورت یک خاصیت تو در تو یا پیچیده /// </summary> public Rating Rating { set; get; } public BlogPost() { Rating = new Rating(); } } }
namespace jQueryMvcSample03.Models { //[ComplexType] public class Rating { public double? TotalRating { get; set; } public int? TotalRaters { get; set; } public double? AverageRating { get; set; } } }
منبع داده فرضی برنامه
using System.Collections.Generic; using System.Linq; using jQueryMvcSample03.Models; namespace jQueryMvcSample03.DataSource { /// <summary> /// منبع داده فرضی /// </summary> public static class BlogPostDataSource { private static IList<BlogPost> _cachedItems; /// <summary> /// با توجه به استاتیک بودن سازنده کلاس، تهیه کش، پیش از سایر فراخوانیها صورت خواهد گرفت /// باید دقت داشت که این فقط یک مثال است و چنین کشی به معنای /// تهیه یک لیست برای تمام کاربران سایت است /// </summary> static BlogPostDataSource() { _cachedItems = createBlogPostsInMemoryDataSource(); } /// <summary> /// هدف صرفا تهیه یک منبع داده آزمایشی ساده تشکیل شده در حافظه است /// </summary> private static IList<BlogPost> createBlogPostsInMemoryDataSource() { var results = new List<BlogPost>(); for (int i = 1; i < 30; i++) { results.Add(new BlogPost { Id = i, Title = "عنوان " + i, Body = "متن ... متن ... متن " + i, Rating = new Rating { TotalRaters = i + 1, AverageRating = 3.5 } }); } return results; } /// <summary> /// پارامترهای شماره صفحه و تعداد رکورد به ازای یک صفحه برای صفحه بندی نیاز هستند /// شماره صفحه از یک شروع میشود /// </summary> public static IList<BlogPost> GetLatestBlogPosts(int pageNumber, int recordsPerPage = 4) { var skipRecords = pageNumber * recordsPerPage; return _cachedItems .OrderByDescending(x => x.Id) .Skip(skipRecords) .Take(recordsPerPage) .ToList(); } } }
در این منبع داده ابتدا لیستی از مطالب تهیه شده و سپس کش میشوند. در ادامه توسط متد GetLatestBlogPosts بازهای از این اطلاعات قابل بازیابی خواهند بود که برای استفاده در حالات صفحه بندی اطلاعات بهینه سازی شده است.
آشنایی با طراحی افزونه jQuery Star Rating
افزودن CSS نمایش امتیازها در ذیل هر مطلب
/* star rating system */ .post_rating { direction: ltr; } .rating { text-indent: -99999px; overflow: hidden; background-repeat: no-repeat; display: inline-block; width: 8px; height: 16px; } .rating.stars { background-image: url('Images/star_rating.png'); } .rating.stars.active { cursor: pointer; } .star-left_off { background-position: -0px -0px; } .star-left_on { background-position: -16px -0px; } .star-right_off { background-position: -8px -0px; } .star-right_on { background-position: -24px -0px; }
افزودن افزونه jQuery Star rating
// <![CDATA[ (function ($) { $.fn.StarRating = function (options) { var defaults = { ratingStarsSpan: '.rating.stars', postInfoUrl: '/', loginUrl: '/login', errorHandler: null, completeHandler: null, onlyOneTimeHandler: null }; var options = $.extend(defaults, options); return this.each(function () { var ratingStars = $(this); $(ratingStars).unbind('mouseover'); $(ratingStars).mouseover(function () { var span = $(this).parent("span"); var newRating = $(this).attr("value"); setRating(span, newRating); }); $(ratingStars).unbind('mouseout'); $(ratingStars).mouseout(function () { var span = $(this).parent("span"); var rating = span.attr("rating"); setRating(span, rating); }); $(ratingStars).unbind('click'); $(ratingStars).click(function () { var span = $(this).parent("span"); var newRating = $(this).attr("value"); var text = span.children("span"); var pID = span.attr("post"); var type = span.attr("sectiontype"); postData({ postID: pID, rating: newRating, sectionType: type }); span.attr("rating", newRating); setRating(span, newRating); }); function setRating(span, rating) { span.find(options.ratingStarsSpan).each(function () { var value = parseFloat($(this).attr("value")); var imgSrc = $(this).attr("class"); if (value <= rating) $(this).attr("class", imgSrc.replace("_off", "_on")); else $(this).attr("class", imgSrc.replace("_on", "_off")); }); } function postData(dataJsonArray) { $.ajax({ type: "POST", url: options.postInfoUrl, data: JSON.stringify(dataJsonArray), contentType: "application/json; charset=utf-8", dataType: "json", complete: function (xhr, status) { var data = xhr.responseText; if (xhr.status == 403) { window.location = options.loginUrl; } else if (status === 'error' || !data) { if (options.errorHandler) options.errorHandler(this); } else if (data == "nok") { if (options.onlyOneTimeHandler) options.onlyOneTimeHandler(this); } else { if (options.completeHandler) options.completeHandler(this); } } }); } }); }; })(jQuery); // ]]>
کاری که این افزونه انجام میدهد ردیابی حرکت ماوس بر روی ستارههای نمایش داده شده و سپس ارسال سه پارامتر ذیل به اکشن متدی که توسط پارامتر postInfoUrl مشخص میگردد، پس از کلیک کاربر میباشد:
{ postID: pID, rating: newRating, sectionType: type }
در اینجا از errorHandler برای نمایش خطاها، از completeHandler برای نمایش تشکر به کاربر و از onlyOneTimeHandler برای نمایش اخطار مثلا «یکبار بیشتر مجاز نیستید به ازای یک مطلب رای دهید»، میتوان استفاده کرد.
بنابراین تا اینجا فایل layout برنامه تقریبا چنین مداخلی را خواهد داشت:
<head> <title>@ViewBag.Title</title> <link href="@Url.Content("Content/starRating.css")" rel="stylesheet" type="text/css" /> <link href="@Url.Content("Content/Site.css")" rel="stylesheet" type="text/css" /> <script src="@Url.Content("~/Scripts/jquery-1.9.1.min.js")" type="text/javascript"></script> <script src="@Url.Content("~/Scripts/jquery.validate.min.js")" type="text/javascript"></script> <script src="@Url.Content("~/Scripts/jquery.unobtrusive-ajax.min.js")" type="text/javascript"></script> <script src="@Url.Content("~/Scripts/jquery.validate.unobtrusive.min.js")" type="text/javascript"></script> <script src="@Url.Content("~/Scripts/jquery.StarRating.js")" type="text/javascript"></script> @RenderSection("JavaScript", required: false) </head>
طراحی یک HTML helper برای نمایش ستارههای امتیاز دهی
ابتدا پوشه استاندارد app_code را به پروژه اضافه کرده و سپس فایلی را به نام StarRatingHelper.cshtml، با محتوای ذیل به آن اضافه نمائید:
@using System.Globalization @helper AddStarRating(int postId, double? average = 0, int? postRatingsCount = 0, string type = "BlogPost", string tooltip = "لطفا جهت رای دادن کلیک نمائید") { string actIt = "active "; if (!average.HasValue) { average = 0; } if (!postRatingsCount.HasValue) { postRatingsCount = 0; } <span class='postRating' rating='@average' post='@postId' title='@tooltip' sectiontype='@type'> @for (double i = .5; i <= 5.0; i = i + .5) { string left; if (i <= average) { left = (i * 2) % 2 == 1 ? "left_on" : "right_on"; } else { left = (i * 2) % 2 == 1 ? "left_off" : "right_off"; } <span class='rating stars @(actIt)star-@left' value='@i'></span> } @if (postRatingsCount > 0) { var ratingInfo = string.Format(CultureInfo.InvariantCulture, "امتیاز {0:0.00} از 5 توسط {1} نفر", average, postRatingsCount); <span>@ratingInfo</span> } else { <span></span> } </span> }
کنترلر ذخیره سازی اطلاعات دریافتی برنامه
using System.Web.Mvc; using System.Web.UI; using jQueryMvcSample03.DataSource; using jQueryMvcSample03.Security; namespace jQueryMvcSample03.Controllers { public class HomeController : Controller { public ActionResult Index() { var postsList = BlogPostDataSource.GetLatestBlogPosts(pageNumber: 0); return View(postsList); //نمایش صفحه اصلی } [HttpPost] [AjaxOnly] [OutputCache(Location = OutputCacheLocation.None, NoStore = true)] public ActionResult SaveRatings(int? postId, double? rating, string sectionType) { if (postId == null || rating == null || string.IsNullOrWhiteSpace(sectionType)) return Content(null); //اعلام بروز خطا if (!this.HttpContext.CanUserVoteBasedOnCookies(postId.Value, sectionType)) return Content("nok"); //اعلام فقط یکبار مجاز هستید رای دهید switch (sectionType) //قسمتهای مختلف سایت که در جداول مختلفی قرار دارند نیز میتوانند گزینه امتیاز دهی داشته باشند { case "BlogPost": //الان شماره مطلب و رای ارسالی را داریم که میتوان نسبت به ذخیره آن اقدام کرد //مثلا //_blogPostsService.SaveRating(postId.Value, rating.Value); break; //... سایر قسمتهای دیگر سایت default: return Content(null); //اعلام بروز خطا } return Content("ok"); //اعلام موفقیت آمیز بودن ثبت اطلاعات } [HttpGet] public ActionResult Post(int? id) { if (id == null) return Redirect("/"); //todo: show the content here return Content("Post " + id.Value); } } }
امضای اکشن متد SaveRatings دقیقا بر اساس سه پارامتر ارسالی توسط jquery.StarRating.js که پیشتر توضیح داده شد، تعیین گردیده است. در این متد ابتدا بررسی میشود که آیا اطلاعاتی دریافت شده است یا خیر. اگر خیر، null را بازگشت خواهد داد. سپس توسط متد CanUserVoteBasedOnCookies بررسی میشود که آیا کاربر میتواند (خصوصا مجددا) رای دهد یا خیر. این افزونه برای رای دهی کاربران وارد نشده به سیستم نیز مناسب است. به همین جهت از کوکیها برای ثبت اطلاعات رای دادن کاربران استفاده گردیده است. پیاده سازی متد CanUserVoteBasedOnCookies را در ادامه ملاحظه خواهید نمود.
در ادامه در متد SaveRatings، یک switch تشکیل شده است تا بر اساس نام قسمت مرتبط به رای گیری، اطلاعات را بتوان به سرویس خاصی در برنامه هدایت کرد. مثلا اطلاعات قسمت مطالب به سرویس مطالب و قسمت نظرات به سرویس نظرات هدایت شوند.
متدهایی برای کار با کوکیها در ASP.NET MVC
using System; using System.Web; namespace jQueryMvcSample03.Security { public static class CookieHelper { public static bool CanUserVoteBasedOnCookies(this HttpContextBase httpContext, int postId, string sectionType) { string key = sectionType + "-" + postId; var value = httpContext.GetCookieValue(key); if (string.IsNullOrWhiteSpace(value)) { httpContext.AddCookie(key, key); return true; } return false; } public static void AddCookie(this HttpContextBase httpContextBase, string cookieName, string value) { httpContextBase.AddCookie(cookieName, value, DateTime.Now.AddDays(30)); } public static void AddCookie(this HttpContextBase httpContextBase, string cookieName, string value, DateTime expires) { var cookie = new HttpCookie(cookieName) { Expires = expires, Value = httpContextBase.Server.UrlEncode(value) // For Cookies and Unicode characters }; httpContextBase.Response.Cookies.Add(cookie); } public static string GetCookieValue(this HttpContextBase httpContext, string cookieName) { var cookie = httpContext.Request.Cookies[cookieName]; if (cookie == null) return string.Empty; //cookie doesn't exist // For Cookies and Unicode characters return httpContext.Server.UrlDecode(cookie.Value); } } }
پیشنهادی در مورد نحوه ذخیره سازی اطلاعات دریافتی
using jQueryMvcSample03.Models; namespace jQueryMvcSample03.DataSource { public interface IBlogPostsService { void SaveRating(int postId, double rating); } public class SampleService : IBlogPostsService { /// <summary> /// یک نمونه از متد ذخیره سازی اطلاعات پیشنهادی /// فقط برای ایده گرفتن /// بدیهی است محل قرارگیری اصلی آن در لایه سرویس برنامه شما خواهد بود /// </summary> public void SaveRating(int postId, double rating) { BlogPost post = null; //post = _blogCtx.Find(postId); // بر اساس شماره مطلب، مطلب یافت شده و فیلدهای آن تنظیم میشوند if (post == null) return; if (!post.Rating.TotalRaters.HasValue) post.Rating.TotalRaters = 0; if (!post.Rating.TotalRating.HasValue) post.Rating.TotalRating = 0; if (!post.Rating.AverageRating.HasValue) post.Rating.AverageRating = 0; post.Rating.TotalRaters++; post.Rating.TotalRating += rating; post.Rating.AverageRating = post.Rating.TotalRating / post.Rating.TotalRaters; // todo: call save changes at the end. } } }
Viewهای برنامه
قسمت پایانی کار ما در اینجا تهیه دو View است:
الف) یک Partial view که لیست مطالب را به همراه گزینه رای دهی به آنها رندر میکند.
ب) View کاملی که از این Partial View استفاده کرده و همچنین افزونه jquery.StarRating.js را فراخوانی میکند.
@using System.Text.RegularExpressions @model IList<jQueryMvcSample03.Models.BlogPost> <ul> @foreach (var item in Model) { <li> <fieldset> <legend>مطلب @item.Id</legend> <h5> @Html.ActionLink(linkText: item.Title, actionName: "Post", controllerName: "Home", routeValues: new { id = item.Id }, htmlAttributes: null) </h5> @item.Body <div class="post_rating"> @Html.Raw(Regex.Replace(@StarRatingHelper.AddStarRating(item.Id, item.Rating.AverageRating, item.Rating.TotalRaters, "BlogPost").ToHtmlString(), @">\s+<", "><")) </div> </fieldset> </li> } </ul>
اگر به کدهای آن دقت کنید از Regex.Replace برای حذف فاصلههای خالی و خطوط جدید بین تگها استفاده گردیده است. اگر اینکار انجام نشود، نیمههای ستارههای نمایش داده شده، با فاصله از یکدیگر رندر میشوند که صورت خوشایندی ندارد.
و نهایتا View ایی که از این اطلاعات استفاده میکنید ساختار زیر را خواهد داشت:
@model IList<jQueryMvcSample03.Models.BlogPost> @{ ViewBag.Title = "Index"; var postInfoUrl = Url.Action(actionName: "SaveRatings", controllerName: "Home"); } <h2> سیستم امتیاز دهی</h2> @{ Html.RenderPartial("_ItemsList", Model); } @section JavaScript { <script type="text/javascript"> $(document).ready(function () { $(".rating.stars.active").StarRating({ ratingStarsSpan: '.rating.stars', postInfoUrl: '@postInfoUrl', loginUrl: '/login', errorHandler: function () { alert('خطایی رخ داده است'); }, completeHandler: function () { alert('با تشکر! رای شما با موفقیت ثبت شد'); }, onlyOneTimeHandler: function () { alert('فقط یکبار میتوانید به ازای هر مطلب رای دهید'); } }); }); </script> }
دریافت کدها و پروژه کامل این قسمت
jQueryMvcSample03.zip
کار با کلیدهای اصلی و خارجی در EF Code first
sv2.Add(t2);
t1.ListTb2.Add(t2);
public class WeatherForecastController : ControllerBase
Assert.IsTrue(outputCompilation.GetDiagnostics().IsEmpty)
references = references.Concat(new[] { MetadataReference.CreateFromFile(typeof(ControllerBase).GetTypeInfo().Assembly.Location), MetadataReference.CreateFromFile(typeof(IActionResult).GetTypeInfo().Assembly.Location) });
<Project Sdk="Microsoft.NET.Sdk"> <ItemGroup> <FrameworkReference Include="Microsoft.AspNetCore.App"/> </ItemGroup> </Project>
new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary)