اگر به گوگل ریدر دقت کرده باشید، دو گزینهی به اشتراک گذاری دارد: 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 مقدار دهی شود. پس از آن به صورت خودکار بر روی کل برنامه تاثیر خواهد گذاشت.
در نگارشهای پیشین ASP.NET MVC، یک چنین مسائلی را با معرفی Child Actionها
public partial class SidebarMenuController : Controller { const int Min15 = 900; [ChildActionOnly] [OutputCache(Duration = Min15)] public virtual ActionResult Index() { return PartialView("_SidebarMenu"); } }
یک مثال: تهیهی اولین View Component
ساختار یک View Component، بسیار شبیه است به ساختار یک Controller، اما با عملکردی محدود. به همین جهت کار تعریف آن با افزودن یک کلاس سیشارپ شروع میشود و این کلاس را میتوان در پوشهای به نام ViewComponents در ریشهی پروژه قرار داد (اختیاری).
سپس برای نمونه، کلاس ذیل را به این پوشه اضافه کنید:
using System.Threading.Tasks; using Microsoft.AspNetCore.Mvc; using Core1RtmEmptyTest.Services; namespace Core1RtmEmptyTest.ViewComponents { public class SiteCopyright : ViewComponent { private readonly IMessagesService _messagesService; public SiteCopyright(IMessagesService messagesService) { _messagesService = messagesService; } public IViewComponentResult Invoke(int numberToTake) { var name = _messagesService.GetSiteName(); return View(viewName: "Default", model: name); } //public async Task<IViewComponentResult> InvokeAsync(int numberToTake) //{ // return View(); //} } }
ساختار کلی یک کلاس ViewComponent شامل دو جزء اصلی است:
الف) از کلاس پایه ViewComponent مشتق میشود. به این ترتیب توسط ASP.NET Core قابل شناسایی خواهد شد.
ب) دارای متد Invoke ایی است که بجای Html.RenderAction در نگارشهای پیشین ASP.NET MVC، قابل فراخوانی است. این متد یک View را باز میگرداند.
ج) در اینجا امکان تعریف نمونهی Async متد Invoke نیز وجود دارد (برای مثال جهت کار با متدهای Async بانک اطلاعاتی).
روش فراخوانی این متدها نیز به این صورت است: ابتدا به دنبال نمونهی async میگردد. اگر یافت شد، همینجا کار خاتمه مییابد. اگر یافت نشد، نمونهی sync یا معمولی آن فراخوانی میشود و اگر این هم یافت نشد، یک استثناء صادر خواهد شد.
د) متد Invoke میتواند دارای پارامترهای دلخواهی نیز باشد و حالت پیش فرض آن بدون پارامتر است.
روش یافتن یک view component توسط ASP.NET Core به این صورت است:
الف) این کلاس باید عمومی بوده و همچنین abstract نباشد.
ب) «یکی» از مشخصههای ذیل را داشته باشد:
1) نامش به ViewComponent ختم شده باشد.
2) از کلاس ViewComponent ارث بری کرده باشد.
3) با ویژگی ViewComponent مزین شده باشد.
نحوه و محل تعریف View یک View Component
پس از تعریف کلاس ViewComponent مورد نظر، اکنون نیاز است View آنرا اضافه کرد. روش یافتن این Viewها توسط ASP.NET Core نیز بر این مبنا است که
الف) اگر این View Component عمومی و سراسری است، باید درون پوشهی shared، پوشهی جدیدی را به نام Components ایجاد کرده و سپس ذیل این پوشه، بر اساس نام کلاس ViewComponent، یک زیر پوشهی دیگر را ایجاد و داخل آن، View مدنظر را اضافه کرد (تصویر ذیل).
/Views/Shared/Components/[NameOfComponent]/Default.cshtml
/Views/[CurrentController]/Components/[NameOfComponent]/Default.cshtml
یک نکته: اگر نام کلاسی به ViewComponent ختم شده بود، نیازی نیست تا ViewComponent را هم در حین ساخت پوشهی آن ذکر کرد.
نحوهی استفادهی از View Component تعریف شده و ارسال پارامتر به آن
و در آخر برای استفادهی از این View Component تعریف شده، به فایل layout برنامه مراجعه کرده و آنرا به نحو ذیل فراخوانی کنید:
<footer> <p>@await Component.InvokeAsync("SiteCopyright", new { numberToTake = 5 })</p> </footer>
یک نکته: متدهای قدیمی Component.Invoke و Component.Renderدر اینجا حذف شدهاند (اگر مقالات پیش از RTM را مطالعه کردید) و روش توصیه شدهی در اینجا، کار با متدهای async است.
تفاوتهای View Components با Child Actions نگارشهای پیشین ASP.NET MVC
پارامترهای یک View Component از طریق یک HTTP Request تامین نمیشوند و همانطور که ملاحظه کردید در همان زمان فراخوانی آنها به صورت مستقیم فراهم خواهند شد. بنابراین مباحث model binding در اینجا دیگر وجود خارجی ندارند. همچنین View Components جزئی از طول عمر یک کنترلر نیستند. بنابراین اکشن فیلترهای مختلف تعریف شده، تاثیری را بر روی آنها نخواهند داشت (این مشکلی بود که با Child Actions در نگارشهای قبلی مشاهده میشد). همچنین View Components به صورت مستقیم از طریق درخواستهای HTTP قابل دسترسی نیستند. به علاوه Child actions قدیمی، از فراخوانیهای async پشتیبانی نمیکنند.
زمانیکه کلاسی از کلاس پایه ViewComponent ارث بری میکند، تنها به این خواص عمومی از درخواست HTTP جاری دسترسی خواهد داشت:
[ViewComponent] public abstract class ViewComponent { protected ViewComponent(); public HttpContext HttpContext { get; } public ModelStateDictionary ModelState { get; } public HttpRequest Request { get; } public RouteData RouteData { get; } public IUrlHelper Url { get; set; } public IPrincipal User { get; } [Dynamic] public dynamic ViewBag { get; } [ViewComponentContext] public ViewComponentContext ViewComponentContext { get; set; } public ViewContext ViewContext { get; } public ViewDataDictionary ViewData { get; } public ICompositeViewEngine ViewEngine { get; set; } //... }
فراخوانی Ajax ایی یک View Component
در ASP.NET Core، یک اکشن متد میتواند خروجی ViewComponent نیز داشته باشد و این تنها روشی است که میتوان یک View Component را از طریق درخواستهای HTTP، مستقیما قابل دسترسی کرد:
public IActionResult AddURLTest() { return ViewComponent("AddURL"); }
$(document).ready (function(){ $("#LoadSignIn").click(function(){ $('#UserControl').load("/Home/AddURLTest"); }); });
امکان بارگذاری View Components از اسمبلیهای دیگر نیز وجود دارد
در مطلب «ارتقاء به ASP.NET Core 1.0 - قسمت 10 - بررسی تغییرات Viewها» روش دسترسی به Viewهای برنامه را که در اسمبلی آن قرار گرفته بودند، بررسی کردیم. دقیقا همان روش در مورد view components نیز صادق است و کاربرد دارد. جهت یادآوری، این مراحل باید طی شوند:
الف) اسمبلی ثالث حاوی View Componentهای برنامه باید ارجاعاتی را به ASP.NET Core و قابلیتهای Razor آن داشته باشد:
"dependencies": { "NETStandard.Library": "1.6.0", "Microsoft.AspNetCore.Mvc": "1.0.0", "Microsoft.AspNetCore.Razor.Tools": { "version": "1.0.0-preview2-final", "type": "build" } }, "tools": { "Microsoft.AspNetCore.Razor.Tools": "1.0.0-preview2-final" }
"buildOptions": { "embed": "Views/**/*.cshtml" }
public void ConfigureServices(IServiceCollection services) { services.AddMvc(); //Get a reference to the assembly that contains the view components var assembly = typeof(ViewComponentLibrary.ViewComponents.SimpleViewComponent).GetTypeInfo().Assembly; //Create an EmbeddedFileProvider for that assembly var embeddedFileProvider = new EmbeddedFileProvider(assembly,"ViewComponentLibrary"); //Add the file provider to the Razor view engine services.Configure<RazorViewEngineOptions>(options => { options.FileProviders.Add(embeddedFileProvider); });
[ViewComponent(Name = "ViewComponentLibrary.Simple")] public class SimpleViewComponent : ViewComponent
@await Component.InvokeAsync("ViewComponentLibrary.Simple", new { number = 5 })
معرفی افزونه CAT.NET
همیشه IDE ویژوال استودیو را در حالت safe mode و بدون افزونهها هم میشود اجرا کرد به صورت زیر:
devenv /safemode
دستور فوق را در run ویندوز نوشته و enter کنید. به این صورت IDE بدون افزونهها بارگذاری میشود.
کلیدهای مربوط به Request
ضروری؟ | نام کلید | مقدار |
بله | "owin.RequestBody" | یک Stream همراه با request body. اگر body برای request وجود نداشته باشد، Stream.Null به عنوان placeholder قابل استفاده است. |
بله | "owin.RequestHeaders" | یک دیکشنری به صورت IDictionary<string, string[]> از هدرهای درخواست. |
بله | "owin.RequestMethod" | رشتهایی حاوی نوع فعل متد HTTP مربوط به درخواست (مانند GET and POST ) |
بله | "owin.RequestPath" | path درخواست شده به صورت string |
بله | "owin.RequestPathBase" | قسمتی از path درخواست به صورت string |
بله | "owin.RequestProtocol" | نام و نسخهی پروتکل (مانند HTTP/1.0 or HTTP/1.1 ) |
بله | "owin.RequestQueryString" | رشتهای حاوی query string ؛ بدون علامت ? (مانند foo=bar&baz=quux ) |
بله | "owin.RequestScheme" | رشتهایی حاوی URL scheme استفاده شده در درخواست (مانند HTTP or HTTPS ) |
ضروری؟ | نام کلید | مقدار |
بله | "owin.ResponseBody" | یک Stream جهت نوشتن response body در خروجی |
بله | "owin.ResponseHeaders" | یک دیکشنری به صورت IDictionary<string, string[]> از هدرهای response |
خیر | "owin.ResponseStatusCode" | یک عدد صحیح حاوی کد وضعیت HTTP response ؛ حالت پیشفرض 200 است. |
خیر | "owin.ResponseReasonPhrase" | یک رشته حاوی reason phrase مربوط به status code ؛ اگر خالی باشد در نتیجه سرور بهتر است آن را مقداردهی کند. |
خیر | "owin.ResponseProtocol" | یک رشته حاوی نام و نسخهی پروتکل (مانند HTTP/1.0 or HTTP/1.1 )؛ اگر خالی باشد؛ “owin.RequestProtocol” به عنوان مقدار پیشفرض در نظر گرفته خواهد شد. |
<package id="Microsoft.Owin" version="3.0.1" targetFramework="net461" /> <package id="Microsoft.Owin.Host.SystemWeb" version="3.0.1" targetFramework="net461" /> <package id="Owin" version="1.0" targetFramework="net461" />
using Owin; namespace SimpleOwinWebApp { public class Startup { public void Configuration(IAppBuilder app) { } } }
using Owin; namespace SimpleOwinWebApp { public class Startup { public void Configuration(IAppBuilder app) { app.Use(async (ctx, next) => { await ctx.Response.WriteAsync("Hello"); }); } }
Func<IOwinContext, Func<Task>, Task> handler
app.Use(async (ctx, next) => { var response = ctx.Environment["owin.ResponseBody"] as Stream; using (var writer = new StreamWriter(response)) { await writer.WriteAsync("Hello"); } });
using System; using Microsoft.Owin.Hosting; namespace SimpleOwinConsoleApp { class Program { static void Main(string[] args) { using (WebApp.Start<Startup>("http://localhost:12345")) { Console.WriteLine("Listening to port 12345"); Console.WriteLine("Press Enter to end..."); Console.ReadLine(); } } } }
using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; namespace SimpleOwinCoreApp { public class Startup { public void ConfigureServices(IServiceCollection services) { } public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory) { loggerFactory.AddConsole(); if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); } app.Run(async (context) => { await context.Response.WriteAsync("Hello World!"); }); } } }
using System.Threading.Tasks; using Microsoft.AspNetCore.Http; namespace SimpleOwinCoreApp.Middlewares { public class SimpleMiddleware { private readonly RequestDelegate _next; public SimpleMiddleware(RequestDelegate next) { _next = next; } public async Task Invoke(HttpContext ctx) { // قبل از فراخوانی میانافزار بعدی await ctx.Response.WriteAsync("Hello DNT!"); await _next(ctx); // بعد از فراخوانی میانافزار بعدی } } }
app.UseMiddleware<SimpleMiddleware>();
"Microsoft.AspNetCore.Owin": "1.0.0"
app.UseOwin(pipeline => { pipeline(next => new MyKatanaBasedMiddleware(next).Invoke) });
using System.Linq; using System.Threading.Tasks; using Microsoft.AspNetCore.Http; namespace SimpleOwinAspNetCore.Middleware { public class IpBlockerMiddleware { private readonly RequestDelegate _next; private readonly IpBlockerOptions _options; public IpBlockerMiddleware(RequestDelegate next, IpBlockerOptions options) { _next = next; _options = options; } public async Task Invoke(HttpContext context) { var ipAddress = context.Request.Host.Host; if (IsBlockedIpAddress(ipAddress)) { context.Response.StatusCode = 403; await context.Response.WriteAsync("Forbidden : The server understood the request, but It is refusing to fulfill it."); return; } await _next.Invoke(context); } private bool IsBlockedIpAddress(string ipAddress) { return _options.Ips.Any(ip => ip == ipAddress); } } }
using System.Collections.Generic; namespace SimpleOwinAspNetCore.Middleware { public class IpBlockerOptions { public IpBlockerOptions() { Ips = new[] { "192.168.1.1" }; } public IList<string> Ips { get; set; } } }
using System.Linq; using Microsoft.AspNetCore.Builder; using Microsoft.Extensions.Configuration; namespace SimpleOwinAspNetCore.Middleware { public static class IpBlockerExtensions { public static IApplicationBuilder UseIpBlocker(this IApplicationBuilder builder, IConfigurationRoot configuration, IpBlockerOptions options = null) { return builder.UseMiddleware<IpBlockerMiddleware>(options ?? new IpBlockerOptions { Ips = configuration.GetSection("block_list").GetChildren().Select(p => p.Value).ToArray() }); } } }
{ "block_list": [ "192.168.1.1", "localhost", "127.0.0.1", "172.16.132.151" ] }
public IConfigurationRoot Configuration { set; get; } public Startup(IHostingEnvironment env) { var builder = new ConfigurationBuilder() .SetBasePath(env.ContentRootPath) .AddJsonFile("blockedIps.json"); Configuration = builder.Build(); }
app.UseIpBlocker(Configuration);
معرفی الگوی Repository
روش متداول کار با فناوریهای مختلف دسترسی به دادهها عموما بدین شکل است:
الف) یافتن رشته اتصالی رمزنگاری شده به دیتابیس از یک فایل کانفیگ (در یک برنامه اصولی البته!)
ب) باز کردن یک اتصال به دیتابیس
ج) ایجاد اشیاء Command برای انجام عملیات مورد نظر
د) اجرا و فراخوانی اشیاء مراحل قبل
ه) بستن اتصال به دیتابیس و آزاد سازی اشیاء
اگر در برنامههای یک تازه کار به هر محلی از برنامه او دقت کنید این 5 مرحله را میتوانید مشاهده کنید. همه جا! قسمت ثبت، قسمت جستجو، قسمت نمایش و ...
مشکلات این روش:
1- حجم کارهای تکراری انجام شده بالا است. اگر قسمتی از فناوری دسترسی به دادهها را به اشتباه درک کرده باشد، پس از مطالعه بیشتر و مشخص شدن نحوهی رفع مشکل، قسمت عمدهای از برنامه را باید اصلاح کند (زیرا کدهای تکراری همه جای آن پراکندهاند).
2- برنامه نویس هر بار باید این مراحل را به درستی انجام دهد. اگر در یک برنامه بزرگ تنها قسمت آخر در یکی از مراحل کاری فراموش شود دیر یا زود برنامه تحت فشار کاری بالا از کار خواهد افتاد (و متاسفانه این مساله بسیار شایع است).
3- برنامه منحصرا برای یک نوع دیتابیس خاص تهیه خواهد شد و تغییر این رویه جهت استفاده از دیتابیسی دیگر (مثلا کوچ برنامه از اکسس به اس کیوال سرور)، نیازمند بازنویسی کل برنامه میباشد.
و ...
همین برنامه نویس پس از مدتی کار به این نتیجه میرسد که باید برای اینکارهای متداول، یک لایه و کلاس دسترسی به دادهها را تشکیل دهد. اکنون هر قسمتی از برنامه برای کار با دیتابیس باید با این کلاس مرکزی که انجام کارهای متداول با دیتابیس را خلاصه میکند، کار کند. به این صورت کد نویسی یک نواختی با حذف کدهای تکراری از سطح برنامه و همچنین بدون فراموش شدن قسمت مهمی از مراحل کاری، حاصل میگردد. در اینجا اگر روزی قرار شد از یک دیتابیس دیگر استفاده شود فقط کافی است یک کلاس برنامه تغییر کند و نیازی به بازنویسی کل برنامه نخواهد بود.
این روزها تشکیل این لایه دسترسی به دادهها (data access layer یا DAL) نیز مزموم است! و دلایل آن در مباحث چرا به یک ORM نیازمندیم برشمرده شده است. جهت کار با ORM ها نیز نیازمند یک لایه دیگر میباشیم تا یک سری اعمال متداول با آنهارا کپسوله کرده و از حجم کارهای تکراری خود بکاهیم. برای این منظور قبل از اینکه دست به اختراع بزنیم، بهتر است به الگوهای طراحی برنامه نویسی شیء گرا رجوع کرد و از رهنمودهای آن استفاده نمود.
الگوی Repository یکی از الگوهای برنامه نویسی با مقیاس سازمانی است. با کمک این الگو لایهای بر روی لایه نگاشت اشیاء برنامه به دیتابیس تشکیل شده و عملا برنامه را مستقل از نوع ORM مورد استفاه میکند. به این صورت هم از تشکیل یک سری کدهای تکراری در سطح برنامه جلوگیری شده و هم از وابستگی بین مدل برنامه و لایه دسترسی به دادهها (که در اینجا همان NHibernate میباشد) جلوگیری میشود. الگوی Repository (مخزن)، کار ثبت، حذف، جستجو و به روز رسانی دادهها را با ترجمه آنها به روشهای بومی مورد استفاده توسط ORM مورد نظر، کپسوله میکند. به این شکل شما میتوانید یک الگوی مخزن عمومی را برای کارهای خود تهیه کرده و به سادگی از یک ORM به ORM دیگر کوچ کنید؛ زیرا کدهای برنامه شما به هیچ ORM خاصی گره نخورده و این عملیات بومی کار با ORM توسط لایهای که توسط الگوی مخزن تشکیل شده، صورت گرفته است.
طراحی کلاس مخزن باید شرایط زیر را برآورده سازد:
الف) باید یک طراحی عمومی داشته باشد و بتواند در پروژههای متعددی مورد استفاده مجدد قرار گیرد.
ب) باید با سیستمی از نوع اول طراحی و کد نویسی و بعد کار با دیتابیس، سازگاری داشته باشد.
ج) باید امکان انجام آزمایشات واحد را سهولت بخشد.
د) باید وابستگی کلاسهای دومین برنامه را به زیر ساخت ORM مورد استفاده قطع کند (اگر سال بعد به این نتیجه رسیدید که ORM ایی به نام XYZ برای کار شما بهتر است، فقط پیاده سازی این کلاس باید تغییر کند و نه کل برنامه).
ه) باید استفاده از کوئریهایی از نوع strongly typed را ترویج کند (مثل کوئریهایی از نوع LINQ).
بررسی مدل برنامه
مدل این قسمت (برنامه NHSample4 از نوع کنسول با همان ارجاعات متداول ذکر شده در قسمتهای قبل)، از نوع many-to-many میباشد. در اینجا یک واحد درسی توسط چندین دانشجو میتواند اخذ شود یا یک دانشجو میتواند چندین واحد درسی را اخذ نماید که برای نمونه کلاس دیاگرام و کلاسهای متشکل آن به شکل زیر خواهند بود:
using System.Collections.Generic;
namespace NHSample4.Domain
{
public class Course
{
public virtual int Id { get; set; }
public virtual string Teacher { get; set; }
public virtual IList<Student> Students { get; set; }
public Course()
{
Students = new List<Student>();
}
}
}
using System.Collections.Generic;
namespace NHSample4.Domain
{
public class Student
{
public virtual int Id { get; set; }
public virtual string Name { get; set; }
public virtual IList<Course> Courses { get; set; }
public Student()
{
Courses = new List<Course>();
}
}
}
کلاس کانفیگ برنامه جهت ایجاد نگاشتها و سپس ساخت دیتابیس متناظر
using FluentNHibernate.Automapping;
using FluentNHibernate.Cfg;
using FluentNHibernate.Cfg.Db;
using NHibernate.Tool.hbm2ddl;
namespace NHSessionManager
{
public class Config
{
public static FluentConfiguration GetConfig()
{
return
Fluently.Configure()
.Database(
MsSqlConfiguration
.MsSql2008
.ConnectionString(x => x.FromConnectionStringWithKey("DbConnectionString"))
)
.Mappings(
m => m.AutoMappings.Add(
new AutoPersistenceModel()
.Where(x => x.Namespace.EndsWith("Domain"))
.AddEntityAssembly(typeof(NHSample4.Domain.Course).Assembly))
.ExportTo(System.Environment.CurrentDirectory)
);
}
public static void CreateDb()
{
bool script = false;//آیا خروجی در کنسول هم نمایش داده شود
bool export = true;//آیا بر روی دیتابیس هم اجرا شود
bool dropTables = false;//آیا جداول موجود دراپ شوند
new SchemaExport(GetConfig().BuildConfiguration()).Execute(script, export, dropTables);
}
}
}
الف) با توجه به اینکه برنامه از نوع ویندوزی است، برای مدیریت صحیح کانکشن استرینگ، فایل App.Config را به برنامه افروده و محتویات آنرا به شکل زیر تنظیم میکنیم (تا کلید DbConnectionString توسط متد GetConfig مورد استفاده قرارگیرد ):
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<connectionStrings>
<!--NHSessionManager-->
<add name="DbConnectionString"
connectionString="Data Source=(local);Initial Catalog=HelloNHibernate;Integrated Security = true"/>
</connectionStrings>
</configuration>
ب) در NHibernate سنتی (!) کار ساخت نگاشتها توسط یک سری فایل xml صورت میگیرد که با معرفی فریم ورک Fluent NHibernate و استفاده از قابلیتهای Auto Mapping آن، اینکار با سهولت و دقت هر چه تمامتر قابل انجام است که توضیحات نحوهی انجام آنرا در قسمتهای قبل مطالعه فرمودید. اگر نیاز بود تا این فایلهای XML نیز جهت بررسی شخصی ایجاد شوند، تنها کافی است از متد ExportTo آن همانگونه که در متد GetConfig استفاده شده، کمک گرفته شود. به این صورت پس از ایجاد خودکار نگاشتها، فایلهای XML متناظر نیز در مسیری که به عنوان آرگومان متد ExportTo مشخص گردیده است، تولید خواهند شد (دو فایل NHSample4.Domain.Course.hbm.xml و NHSample4.Domain.Student.hbm.xml را در پوشهای که محل اجرای برنامه است خواهید یافت).
با فراخوانی متد CreateDb این کلاس، پس از ساخت خودکار نگاشتها، database schema متناظر، در دیتابیسی که توسط کانکشن استرینگ برنامه مشخص شده، ایجاد خواهد شد که دیتابیس دیاگرام آنرا در شکل ذیل مشاهده مینمائید (جداول دانشجویان و واحدها هر کدام به صورت موجودیتی مستقل ایجاد شده که ارجاعات آنها در جدولی سوم نگهداری میشود).
پیاده سازی الگوی مخزن
اینترفیس عمومی الگوی مخزن به شکل زیر میتواند باشد:
using System;
using System.Linq;
using System.Linq.Expressions;
namespace NHSample4.NHRepository
{
//Repository Interface
public interface IRepository<T>
{
T Get(object key);
T Save(T entity);
T Update(T entity);
void Delete(T entity);
IQueryable<T> Find();
IQueryable<T> Find(Expression<Func<T, bool>> predicate);
}
}
سپس پیاده سازی آن با توجه به کلاس SingletonCore ایی که در قسمت قبل تهیه کردیم (جهت مدیریت صحیح سشن فکتوری)، به صورت زیر خواهد بود.
این کلاس کار آغاز و پایان تراکنشها را نیز مدیریت کرده و جهت سهولت کار اینترفیس IDisposable را نیز پیاده سازی میکند :
using System;
using System.Linq;
using NHSessionManager;
using NHibernate;
using NHibernate.Linq;
namespace NHSample4.NHRepository
{
public class Repository<T> : IRepository<T>, IDisposable
{
private ISession _session;
private bool _disposed = false;
public Repository()
{
_session = SingletonCore.SessionFactory.OpenSession();
BeginTransaction();
}
~Repository()
{
Dispose(false);
}
public T Get(object key)
{
if (!isSessionSafe) return default(T);
return _session.Get<T>(key);
}
public T Save(T entity)
{
if (!isSessionSafe) return default(T);
_session.Save(entity);
return entity;
}
public T Update(T entity)
{
if (!isSessionSafe) return default(T);
_session.Update(entity);
return entity;
}
public void Delete(T entity)
{
if (!isSessionSafe) return;
_session.Delete(entity);
}
public IQueryable<T> Find()
{
if (!isSessionSafe) return null;
return _session.Linq<T>();
}
public IQueryable<T> Find(System.Linq.Expressions.Expression<Func<T, bool>> predicate)
{
if (!isSessionSafe) return null;
return Find().Where(predicate);
}
void Commit()
{
if (!isSessionSafe) return;
if (_session.Transaction != null &&
_session.Transaction.IsActive &&
!_session.Transaction.WasCommitted &&
!_session.Transaction.WasRolledBack)
{
_session.Transaction.Commit();
}
else
{
_session.Flush();
}
}
void Rollback()
{
if (!isSessionSafe) return;
if (_session.Transaction != null && _session.Transaction.IsActive)
{
_session.Transaction.Rollback();
}
}
private bool isSessionSafe
{
get
{
return _session != null && _session.IsOpen;
}
}
void BeginTransaction()
{
if (!isSessionSafe) return;
_session.BeginTransaction();
}
public void Dispose()
{
Dispose(true);
// tell the GC that the Finalize process no longer needs to be run for this object.
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposeManagedResources)
{
if (_disposed) return;
if (!disposeManagedResources) return;
if (!isSessionSafe) return;
try
{
Commit();
}
catch (Exception ex)
{
Console.WriteLine(ex.ToString());
Rollback();
}
finally
{
if (isSessionSafe)
{
_session.Close();
_session.Dispose();
}
}
_disposed = true;
}
}
}
using System;
using System.Collections.Generic;
using NHSample4.Domain;
using NHSample4.NHRepository;
namespace NHSample4
{
class Program
{
static void Main(string[] args)
{
//ایجاد دیتابیس در صورت نیاز
//NHSessionManager.Config.CreateDb();
//ابتدا یک دانشجو را اضافه میکنیم
Student student = null;
using (var studentRepo = new Repository<Student>())
{
student = studentRepo.Save(new Student() { Name = "Vahid" });
}
//سپس یک واحد را اضافه میکنیم
using (var courseRepo = new Repository<Course>())
{
var course = courseRepo.Save(new Course() { Teacher = "Shams" });
}
//اکنون یک واحد را به دانشجو انتساب میدهیم
using (var courseRepo = new Repository<Course>())
{
courseRepo.Save(new Course() { Students = new List<Student>() { student } });
}
//سپس شماره دروس استادی خاص را نمایش میدهیم
using (var courseRepo = new Repository<Course>())
{
var query = courseRepo.Find(t => t.Teacher == "Shams");
foreach (var course in query)
Console.WriteLine(course.Id);
}
Console.WriteLine("Press a key...");
Console.ReadKey();
}
}
}
همانطور که ملاحظه میکنید در این سطح دیگر برنامه هیچ درکی از ORM مورد استفاده ندارد و پیاده سازی نحوهی تعامل با NHibernate در پس کلاس مخزن مخفی شده است. کار آغاز و پایان تراکنشها به صورت خودکار مدیریت گردیده و همچنین آزاد سازی منابع را نیز توسط اینترفیس IDisposable مدیریت میکند. به این صورت امکان فراموش شدن یک سری از اعمال متداول به حداقل رسیده، میزان کدهای تکراری برنامه کم شده و همچنین هر زمانیکه نیاز بود، صرفا با تغییر پیاده سازی کلاس مخزن میتوان به ORM دیگری کوچ کرد؛ بدون اینکه نیازی به بازنویسی کل برنامه وجود داشته باشد.
دریافت سورس برنامه قسمت هشتم
ادامه دارد ...
وضعیت خروجی متدهای بازنویسی شده تا پیش از C# 9.0
برای توضیح بهتر Covariant returns، نیاز است مثال زیر را بررسی کنیم:
public abstract class Product { public string Name { get; set; } public abstract ProductOrder Order(int quantity); } public class Book : Product { public string ISBN { get; set; } public override ProductOrder Order(int quantity) => new BookOrder { Quantity = quantity, Product = this }; } public class ProductOrder { public int Quantity { get; set; } } public class BookOrder : ProductOrder { public Book Product { get; set; } }
همانطور که مشاهده میکنید، در کلاس Book، تنها خروجی که برای متد Order بازنویسی شده میتوان درنظر گرفت، همانی است که در کلاس پایهی Product تعریف شدهاست و قابل تغییر نیست؛ یعنی همان ProductOrder.
همچنین در حین استفادهی از این کلاسها، تبدیل خروجی متد Order، به BookOrder ضروری است:
var book = new Book { Name = "My book", ISBN = "11-1-12-22-0" }; BookOrder orderBook = (BookOrder)book.Order(1);
امکان تغییر خروجی متدهای بازنویسی شده در C# 9.0
در C# 9.0 با مجاز اعلام شدن خروجیهای covariant، میتوان تغییرات زیر را به کدهای فوق اعمال کرد:
public class Book : Product { public string ISBN { get; set; } public override BookOrder Order(int quantity) => new BookOrder { Quantity = quantity, Product = this }; }
مزایای این ویژگی:
- داشتن یک خروجی مختص و متناسب با کلاس کتاب، مانند BookOrder؛ بجای ارائهی یک خروجی بسیار عمومی ProductOrder.
- نیاز به کار با Generics را برای اینگونه اختصاصی سازیها منتفی میکند.
- با این تغییر، دیگر نیازی به تبدیل نوع خروجی متد Order یک کتاب نیست و سطر سفارش دهی را میتوان به صورت زیر خلاصه کرد:
BookOrder orderBook = book.Order(1);
در قسمت قبلی، نحوه تعریف کلاس جنریک شرح داده شد و در سری دوم اشارهای به مفاهیم و نحوه پیاده سازی اینترفیس جنریک میپردازیم.
مفهوم اینترفیس جنریک همانند مفهوم اینترفیس در دات نت است. با این تفاوت که برای آنها یک نوع عمومی تعریف میشود و نوع آنها در زمان اجرا تعیین خواهد شد و کلاس بر اساس نوع اینترفیس، اینترفیس را پیاده سازی میکند. برای درک بهتر به نحوه تعریف اینترفیس جنریک زیر دقت کنید:
public interface IBinaryOperations<T> { T Add(T arg1, T arg2); T Subtract(T arg1, T arg2); T Multiply(T arg1, T arg2); T Divide(T arg1, T arg2); }
public class BasicMath : IBinaryOperations<int> { public int Add(int arg1, int arg2) { return arg1 + arg2; } public int Subtract(int arg1, int arg2) { return arg1 - arg2; } public int Multiply(int arg1, int arg2) { return arg1 * arg2; } public int Divide(int arg1, int arg2) { return arg1 / arg2; } }
static void Main(string[] args) { Console.WriteLine("***** Generic Interfaces *****\n"); BasicMath m = new BasicMath(); Console.WriteLine("1 + 1 = {0}", m.Add(1, 1)); Console.ReadLine(); }
public class BasicMath : IBinaryOperations<double> { public double Add(double arg1, double arg2) { return arg1 + arg2; } ... }
اکثر خدمات گوگل دارای API هم هستند و به این ترتیب با استفاده از برنامه نویسی نیز میتوان به آنها دسترسی پیدا کرد. برای نمونه API دسترسی به Blogger در اینجا توضیح داده شده است. برای کار با این امکانات یا میتوان چرخ را از نو اختراع کرد یا از کتابخانههای مرتبطی همانند Gdata API for .NET استفاده نمود. برای دات نت فریم ورک، از آدرس http://code.google.com/p/google-gdata/ میتوان آخرین کتابخانههای کار با GData یا Google Data API را دریافت کرد. برای نمونه فایل Google_Data_API_Setup_1.9.0.0.msi فعلی آن حدود 28 مگ حجم دارد و به درد کسانی میخورد که علاقمند هستند تا تمام امکانات موجود آنرا بررسی کنند. راه سادهتری هم برای دسترسی به این کتابخانهها وجود دارد؛ میتوان از NuGet استفاده کرد.
به این ترتیب به سادگی و سرعت هرچه تمامتر فایل 200 کیلوبایتی Google.GData.Client.dll دریافت شده و ارجاعی نیز به آن اضافه خواهد شد. همین حد جهت کار با بلاگر کافی است.
برای نمونه قطعه کد زیر کار ارسال یک مطلب جدید به وبلاگ بلاگری شما را انجام خواهد داد:
using System;
using System.Collections.Generic;
using Google.GData.Client;
namespace BloggerAutoPoster
{
public class BloggerAutoPoster
{
public string UserName { set; get; }
public string Password { set; get; }
public string PostTitle { set; get; }
public IList<string> PostTags { set; get; }
public string PostBody { set; get; }
public string BlogUrl { set; get; }
public bool PostAsDraft { set; get; }
public bool PostNewEntry()
{
var service = new Service("blogger", "blogger-example")
{
Credentials = new GDataCredentials(UserName, Password)
};
var newPost = constructNewEntry();
var result = service.Insert(new Uri(BlogUrl), newPost);
return result != null;
}
private AtomEntry constructNewEntry()
{
var newPost = new AtomEntry
{
Title = { Text = PostTitle },
Content = new AtomContent
{
Content = string.Format(@"<div xmlns=""http://www.w3.org/1999/xhtml"">{0}</div>", PostBody),
Type = "xhtml"
},
IsDraft = PostAsDraft
};
foreach (var tag in PostTags)
{
newPost.Categories.Add(
new AtomCategory
{
Term = tag,
Scheme = "http://www.blogger.com/atom/ns#"
});
}
return newPost;
}
}
}
مثالی از استفاده آن هم به صورت زیر میباشد:
new BloggerAutoPoster
{
BlogUrl = "https://www.blogger.com/feeds/number/posts/default",
UserName = "name@gmail.com",
Password = "pass",
PostTitle = "بررسی ارسل خودکار-3",
PostTags = new List<string> { "بررسی ارسال خودکار" },
PostBody = "تست میشود123",
PostAsDraft = false
}.PostNewEntry();
نام کاربری و کلمه عبور آن، همان مشخصات وارد شدن به اکانت جیمیل شما است. اگر میخواهید مطلب ارسالی بلافاصله در سایت ظاهر نشود PostAsDraft را true کنید. همچنین BlogUrl آن، همانطور که ملاحظه میکنید فرمت خاصی دارد. جهت یافتن آن میتوان از قطعه کد زیر کمک گرفت:
using System;
using System.Collections.Generic;
using System.Linq;
using Google.GData.Client;
namespace BloggerAutoPoster
{
public class BlogInfo
{
public string Title { set; get; }
public string Url { set; get; }
}
public class BloggerInfo
{
public static IList<BlogInfo> FindMyBlogsUrls(string username, string password)
{
var result = new List<BlogInfo>();
var service = new Service("blogger", "blogger-example")
{
Credentials = new GDataCredentials(username, password)
};
var query = new FeedQuery { Uri = new Uri("https://www.blogger.com/feeds/default/blogs") };
var feed = service.Query(query);
if (feed == null)
throw new NotSupportedException("You don't have any blogs!");
foreach (var entry in feed.Entries)
{
result.AddRange(entry.Links.Where(t => t.Rel.Equals("http://schemas.google.com/g/2005#post"))
.Select(t => new BlogInfo
{
Url = new Uri(t.HRef.ToString()).AbsoluteUri,
Title = entry.Title.Text
}));
}
return result;
}
}
}
توسط کد فوق، آدرس ویژه و عنوان تمام بلاگهای ثبت شدهی بلاگری شما بازگشت داده میشود.
تغییر نام یک فایل
کسی جواب نداد خودم دست به کار شدم.
در مورد مشکل بالا در واقع یک Partial View آماده کردند به نام _breadCrumb.cshtmlکه کدش در پایین هست.
@using System.IO; @{ var pathsplited = new string[] { }; if (ViewBag.CurrentPath != null) { pathsplited = ViewBag.CurrentPath.Split(Path.DirectorySeparatorChar); } string Host = Request.Url.Authority + "/" + MvcFileManager.Services.Helper.PathBrowseinWebConfig; string pathcombine = ""; } @Html.ActionLink(Host , "Browse", new { path = pathcombine }) / @if (pathsplited != null) { foreach (var s in pathsplited) { if (s != "") { pathcombine += s + @"\"; @Html.ActionLink(s, "Browse", new { path = pathcombine }) @Html.Raw(" / ") ; } } }
و کد بالا را باز نویسی کردم به کد پایین
@using System.Diagnostics @using MvcFileManager.Services @{ var pathsplited = new string[] { }; if (ViewBag.CurrentPath != null) { pathsplited = ViewBag.CurrentPath.Split(Path.DirectorySeparatorChar); } Debug.Assert(Request.Url != null, "Request.Url != null"); string host = Request.Url.Authority + "/" + Helper.PathBrowseinWebConfig; string pathcombine = ""; } @Html.ActionLink(host , "Browse", new { path = pathcombine }) @if (pathsplited != null) { foreach (var s in pathsplited) { if (s.Trim() != "") { @Html.Raw(" / ") pathcombine += s; pathcombine += Helper.FileFolderCheck(pathcombine) == FileFolderStatus.File ? "" : @"\"; @Html.ActionLink(s, "Browse", new { path = pathcombine }) } } @Html.Raw(Helper.FileFolderCheck(ViewBag.CurrentPath)==FileFolderStatus.File ? "" : (" / ")) }
که مشکل برطرف شد.
و یه مورد هایی در استفاده از / برای جدا کردن فایلها و پوشهها بود که اصلاح شد.
با تشکر