TailSpin
استفاده از Web API در ASP.NET Web Forms
- Web API یک بحث سمت سرور است. به آن به زبان ساده به چشم یک وب سرویس مدرن نگاه کنید. برای نمونه بجای وبمتدهای استاتیک صفحات aspx یا فایلهای ashx یا asmx و حتی سرویسهای WCF از نوع REST و امثال آن، بهتر است از Web API استفاده کنید.
- برای نمونه پایه مباحثی مانند Forms Authentication در اینجا هم کاربرد دارد (البته این یک نمونه است).
- برای کار با Web API الزاما نیازی به ASP.NET ندارید (نه وب فرمها و نه MVC)؛ به هیچکدام از نگارشهای آن. سمت کاربر آن AngularJS و سمت سرور آن Web API باشد. کار میکند. (اهمیت این مساله در اینجا است که الان میشود یک فریم ورک جدید توسعهی برنامههای وب را کاملا مستقل از وب فرمها و MVC طراحی کرد)
استاد گرامی پرسشی داشتم
ما در سازمانمان نرم افزاری داریم که کاربران آن در Active directory تعریف شده اند.
برنامه با وب سرویس در ارتباط است که در یک سروری قرار گرفته است که آن سرور با یک سرور دیگر از طریق کابل شبکه در ارتباط است.
سرور دوم سروری است که پایگاه داده روی آن قرار گرفته است.
با توجه به اینکه با WCF کاربر جاری برنامه را می توان به دست آورد؛ ما کاربر جاری را تا سطح سرور 1 می آوریم ولی برای ارسال آن به اس کیو ال دو راه حل داریم.
راهی که هم اکنون از آن استفاده می کنیم این است که Connection string مان تک کاربره است و دیگر اینکه هم اکنون که کاربر جاری را داریم همان را با Connection string به سمت SQL بفرستیم و که در نتیجه گزارش گیری و مانیتورینگ بسیار خوبی خواهیم داشت. ولی باید همه کاربران در SQL تعریف شوند چون سرور پایگاه داده به دومین متصل نیست.
به نظر شما در صورتی که سرورهای نسبتاً خوبی از لحاظ سخت افزاری داشته باشیم و کاربرانی در حدود 2000 نفر به طور کلی و 200 نفر همزمان داشته باشیم، Connection string تک کاربره بهتر است یا چند کاربره؟
با سپاس فراوان
اصولا خوندن کتاب کاغذی را نسبت به PDF ترجیح میدم اگه فارسی هم باشه چه بهتر
به نظر من کتابهای فارسی :
اکثرا ترجمه روانی ندارند یا بعضی از لغتها بهتره ترجمه نشه که ترجمه شده (بعضی وقتها فکر میکنی گوگل ترجمه کرده یا یک فرد غیر متخصص که تجربه ای در این رشته نداره)
این کتابها بومی نشده اند (اگه تالیف یک کتاب سخته حداقل میتونه لابه لای مطالب اون نکات و یا ابزارهایی که برای خواننده ایرانی مهم است باشه )
نمونه بد اون
مثلا کتاب Visual C# 2010 ترجمه احمد پهلوان انتشارات ناقوس که فصل آخر اون که راجب وب سرویس هست هر جایی که باید مینوشته WCF به اشتباه نوشته WPF و خیلی مشکلات دیگه
و یه نمونه خوب که مشکلات بالا رو نداره
سری آموزشی ASP.NET MVC وحید نصیری که اگه چاپ شده بود حتما میخریدمش
[ServiceBehavior( IncludeExceptionDetailInFaults = true)] public class BookService : IBookService { public BookService(IBookRepository bookRepository) { Repository = bookRepository; } public IBookRepository Repository { get; private set; } public IList<Entity.Book> GetAll() { return Repository.GetAll(); } }
var container = new UnityContainer(); container.RegisterType<IBookRepository, BookRepository>(); container.RegisterType<BookService, BookService>(); ServiceLocator.SetLocatorProvider(new ServiceLocatorProvider(() => { return container; }));
public class UnityInstanceProvinder : IInstanceProvider { private Type serviceType; public UnityInstanceProvinder( Type serviceType ) { this.serviceType = serviceType; } public object GetInstance( InstanceContext instanceContext, Message message ) { return ServiceLocator.Current.GetInstance( serviceType ); } public object GetInstance( InstanceContext instanceContext ) { return GetInstance( instanceContext, null ); } public void ReleaseInstance( InstanceContext instanceContext, object instance ) { if ( instance is IDisposable ) { ( ( IDisposable )instance ).Dispose(); } } }
با پیاده سازی متدهای اینترفیس IInstanceProvider میتوان عملیات وهله سازی سرویسهای WCF را تغییر داد. متد GetInstance همین کار را برای ما انجام خواهد داد. در این متد ما با توجه به نوع ServiceType سرویس مورد نظر را از ServiceLocator تامین خواهیم کرد. چون وابستگیهای سرویس هم در IOC Cotnainer موجود است در نتیجه سرویس به درستی وهله سازی خواهد شد. از آن جا که در WCF عملیات وهله سازی از سرویسها به طور مستقیم به نوع سرویس بستگی دارد، هیچ نیازی به نوع Contract مربوطه نیست. در نتیجه Service Type به صورت مستقیم در اختیار این کلاس قرار خواهد گرفت. مرحله آخر معرفی IInstanceProvider به عنوان یک Service Behavior است. برای این کار کدهای زیر را در کلاسی به نام UnityInstanceProviderContext کپی نمایید:
public class UnityInstanceProviderContext : IServiceBehavior { public void AddBindingParameters( ServiceDescription serviceDescription , ServiceHostBase serviceHostBase , Collection<ServiceEndpoint> endpoints , BindingParameterCollection bindingParameters ) { } public void ApplyDispatchBehavior( ServiceDescription serviceDescription , ServiceHostBase serviceHostBase ) { serviceHostBase.ChannelDispatchers.ToList().ForEach( channelDispatcherBase => { var channelDispatcher = ( channelDispatcherBase as ChannelDispatcher ); if ( channelDispatcher != null ) { channelDispatcher.Endpoints.ToList().ForEach( endpoint => { endpoint.DispatchRuntime.InstanceProvider = new UnityInstanceProvinder( serviceDescription.ServiceType ); } ); } } ); } public void Validate( ServiceDescription serviceDescription , ServiceHostBase serviceHostBase ) { } }
در هنگام هاست سرویس مورد نظر هم باید تمام این عملیات به عنوان یک Behavior در اختیار ُService Host قرار گیرد.همانند نمونه کد ذیل:
using (ServiceHost host = new ServiceHost(typeof(BookService))) { host.Description.Behaviors.Add( new UnityInstanceProviderContext() ); }
IIS Hosting:
if (request.Properties.ContainsKey["MS_HttpContext"]) { var ctx = request.Properties["MS_HttpContext"] as HttpContextWrapper; if (ctx != null) { var ip = ctx.Request.UserHostAddress; } }
public static class HttpRequestMessageExtensions { private const string HttpContext = "MS_HttpContext"; public static string GetClientIpAddress(this HttpRequestMessage request) { if (request.Properties.ContainsKey(HttpContext)) { dynamic ctx = request.Properties[HttpContext]; if (ctx != null) { return ctx.Request.UserHostAddress; } } return null; } }
Self Hosting:
if (request.Properties.ContainsKey[RemoteEndpointMessageProperty.Name]) { var remote = request.Properties[RemoteEndpointMessageProperty.Name] as RemoteEndpointMessageProperty; if (remote != null) { var ip = remote.Address; } }
ترکیب حالتهای قبلی:
»در این صورت دیگر نیازی به اضافه کردن اسمبلی System.ServiceModel نیست.
public static class HttpRequestMessageExtensions { private const string HttpContext = "MS_HttpContext"; private const string RemoteEndpointMessage = "System.ServiceModel.Channels.RemoteEndpointMessageProperty"; public static string GetClientIpAddress(this HttpRequestMessage request) { if (request.Properties.ContainsKey(HttpContext)) { dynamic ctx = request.Properties[HttpContext]; if (ctx != null) { return ctx.Request.UserHostAddress; } } if (request.Properties.ContainsKey(RemoteEndpointMessage)) { dynamic remoteEndpoint = request.Properties[RemoteEndpointMessage]; if (remoteEndpoint != null) { return remoteEndpoint.Address; } } return null; } }
public class MyHandler : DelegatingHandler { private readonly HashSet<string> deniedIps; protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) { if (deniedIps.Contains(request.GetClientIpAddress())) { return Task.FromResult( new HttpResponseMessage( HttpStatusCode.Unauthorized ) ); } return base.SendAsync(request, cancellationToken); } }
Owin :
زمانی که از Owin برای هاست سرویسهای Web Api خود استفاده میکنید کمی روال انجام کار متفاوت خواهد شد. در این مورد نیز میتوانید از DelegatingHandlerها استفاده کنید. معرفی DelegatingHandler طراحی شده به Asp.Net PipeLine به صورت زیر خواهد بود:
public class Startup { public void Configuration( IAppBuilder appBuilder ) { var config = new HttpConfiguration(); var routeHandler = HttpClientFactory.CreatePipeline( new HttpControllerDispatcher( config ), new DelegatingHandler[] { new MyHandler(), } ); config.Routes.MapHttpRoute( name: "Default", routeTemplate: "{controller}/{action}", defaults: null, constraints: null, handler: routeHandler ); config.EnsureInitialized(); appBuilder.UseWebApi( config ); } }
public class IpMiddleware : OwinMiddleware { private readonly HashSet<string> _deniedIps; public IpMiddleware(OwinMiddleware next, HashSet<string> deniedIps) : base(next) { _deniedIps = deniedIps; } public override async Task Invoke(OwinRequest request, OwinResponse response) { var ipAddress = (string)request.Environment["server.RemoteIpAddress"]; if (_deniedIps.Contains(ipAddress)) { response.StatusCode = 403; return; } await Next.Invoke(request, response); } }
در نهایت برای معرفی این Middleware طراحی شده به Application، مراحل زیر را انجام دهید.
public class Startup { public void Configuration( IAppBuilder appBuilder ) { var config = new HttpConfiguration(); var deniedIps = new HashSet<string> {"192.168.0.100", "192.168.0.101"};
app.Use(typeof(IpMiddleware), deniedIps); appBuilder.UseWebApi( config ); } }
نکته : آشنایی با مفاهیم اولیه WCF برای درک بهتر مطالب الزامی است.
در ابتدا لازم است تا مدل برنامه را تعریف کنیم. ابتدا یک پروژه از نوع WCF Service Application ایجاد کنید و مدل زیر را بسازید.
#Employee
[DataContract] public class Employee { [DataMember] public string Name { get; set; } [DataMember] public Employee Manager { get; set; } }
[DataContract] public class Department { [DataMember] public string DeptName { get; set; } [DataMember] public List<Employee> Staff { get; set; } }
#Contract
[ServiceContract] public interface IDepartmentService { [OperationContract] Department GetOneDepartment(); }
public class DepartmentService : IDepartmentService { public Department GetOneDepartment() { List<Employee> listOfEmployees = new List<Employee>(); var masoud = new Employee() { Name = "Masoud" }; var saeed = new Employee() { Name = "Saeed", Manager = masoud }; var peyman = new Employee() { Name = "Peyman", Manager = saeed }; var mostafa = new Employee() { Name = "Mostafa", Manager = saeed }; return new Department() { DeptName = "IT", Staff = new List<Employee>() { masoud, saeed, peyman, mostafa } }; } }
class Program { static void Main( string[] args ) { DepartmentServiceClient client = new DepartmentServiceClient(); var result = client.GetOneDepartment(); WriteDataToFile( result ); Console.ReadKey(); } private static void WriteDataToFile( Department data ) { DataContractSerializer dcs = new DataContractSerializer( typeof( Department ) ); var ms = new MemoryStream(); dcs.WriteObject( ms, data ); ms.Seek( 0, SeekOrigin.Begin ); var sr = new StreamReader( ms ); var xml = sr.ReadToEnd(); string filePath = @"d:\\data.xml"; if ( !File.Exists( filePath ) ) { File.Create( filePath ); } using ( TextWriter writer = new StreamWriter( filePath ) ) { writer.Write( xml ); } }
<Department xmlns="http://schemas.datacontract.org/2004/07/Service" xmlns:i="http://www.w3.org/2001/XMLSchema-instance"> <Name>IT</Name> <Staff> <Employee> <Manager i:nil="true"/> <Name>Masoud</Name> </Employee> <Employee> <Manager> <Manager i:nil="true"/> <Name>Masoud</Name> </Manager> <Name>Saeed</Name> </Employee> <Employee> <Manager> <Manager> <Manager i:nil="true"/> <Name>Masoud</Name> </Manager> <Name>Saeed</Name> </Manager> <Name>Peyman</Name> </Employee> <Employee> <Manager> <Manager> <Manager i:nil="true"/> <Name>Masoud</Name> </Manager> <Name>Saeed</Name> </Manager> <Name>Mostafa</Name> </Employee> </Staff> </Department>
[DataContract( IsReference = true )] public class Employee { [DataMember] public string Name { get; set; } [DataMember] public Employee Manager { get; set; } }
<Department xmlns="http://schemas.datacontract.org/2004/07/Service" xmlns:i="http://www.w3.org/2001/XMLSchema-instance"> <Name>IT</Name> <Staff> <Employee z:Id="i1" xmlns:z="http://schemas.microsoft.com/2003/10/Serialization/"> <Manager i:nil="true"/> <Name>Masoud</Name> </Employee> <Employee z:Id="i2" xmlns:z="http://schemas.microsoft.com/2003/10/Serialization/"> <Manager z:Ref="i1"/> <Name>Saeed</Name> </Employee> <Employee z:Id="i3" xmlns:z="http://schemas.microsoft.com/2003/10/Serialization/"> <Manager z:Ref="i2"/> <Name>Peyman</Name> </Employee> <Employee z:Id="i4" xmlns:z="http://schemas.microsoft.com/2003/10/Serialization/"> <Manager z:Ref="i2"/> <Name>Mostafa</Name> </Employee> </Staff> </Department>
مزایا :استفاده از این روش در هنگام عمل سریالایز دادههای زیاد و زمانی که تعداد Objectهای موجود در ObjectGraph زیاد باشد باعث افزایش کارایی و سرعت انجام عملیات سریالایز میشود.
این روش دارای تمام خواص یک تراکنش است(اصطلاحا به این خواص ACID Properties گفته میشود)
1-Atomic : به این معناست که تمام دستورات بین بلاک (دستورات SQL و سایر عملیات) باید به صورت عملیات اتمی کار کنند. یعنی یا تمام عملیات موفقیت آمیز است یا همه با شکست روبرو میشوند.
2 - Consistent: به این معناست که اگر تراکنش موفقیت آمیز بود پایگاه داده باید در شروع تراکنش بعدی تغییرات لازم رو انجام داده باشد و در غیر این صورت پایگاه داده باید به حالت قبل از شروع تراکنش برگردد.
3- Isolated: اگر چند تا تراکنش هم زمان شروع شوند اجرای هیچ کدوم از اونها نباید بر اجرای بقیه تاثیر بزاره.
4- Durable: یعنی تغییرات حاصل شده بعد از اتمام تراکنش باید دائمی باشند.
روش کار به این صورت است تمام کارهایی که قصد داریم در طی یک تراکنش انجام شوند باید در یک بلاک قرار بگیرند و تا زمانی که متد Complete فراخوانی شود. در این بلاک شما هر عملیاتی رو که به عنوان جزئی از تراکنش میدونید قرار بدید. در صورتی که کنترل اجرا به فراخوانی دستور Complete برسه تمام موارد قبل از این دستور Commit میشوند در غیر این صورت RollBack.
به مثال زیر دقت کنید.
ابتدا به پروژه مربوطه باید اسمبلی System.Transaction رو اضافه کنید.
using ( TransactionScope scope = new TransactionScope() ) { //Statement1 //Statement2 //Statement3 scope.Complete(); }
using ( System.Transactions.TransactionScope scope = new System.Transactions.TransactionScope() ) { if ( result == 0 ) { throw new ApplicationException(); } scope.Complete(); }
نکته 1: میزان Timeout در این تراکنشها چه قدر است؟
برای بدست آوردن مقدار Timeout در این گونه تراکنشها میتوانید از کلاس TransactionManager استفاده کنید. به صورت زیر :
var defaultTimeout = TransactionManager.DefaultTimeout var maxTimeout = TransactionManager.MaximumTimeout
TransactionOptions option = new TransactionOptions(); option.Timeout = TimeSpan.MaxValue;
using ( System.Transactions.TransactionScope scope = new System.Transactions.TransactionScope(TransactionScopeOption.Required ,option) ) { scope.Complete(); }
1 - Required : یعنی نیاز به تراکنش وجود دارد. در صورتی که تراکنش در یک تراکنش دیگر شروع شود نیاز به ساختن تراکنش جدید نیست و از همان تراکنش قبلی برای این کار استفاده میشود.
2 - RequiresNew: در هر صورت برای محدوده یک تراکنش تولید میشود.
3- Suppress : به عنوان محدوه تراکنش در نظر گرفته نمیشود.
using(TransactionScope scope1 = new TransactionScope()) { try { using(TransactionScope scope2 = new TransactionScope(TransactionScopeOption.Suppress)) { //به دلیل استفاده از Suppress این محدوده خارج از تراکنش محسوب میشود } //شروع محدوده تراکنش } catch {} //Rest of scope1 }
1-این روش از تراکنشهای توزیع شده پشتیبانی میکند . یعنی میتونید از چند تا منبع داده استفاده کنید یا میتونید از یک تراکنش چند تا Connection به یک منبع داده باز کنید.(استفاده از چند تاconnection در طی یک تراکنش)
using (TransactionScope scope = new TransactionScope(TransactionScopeOption.Required)) { string strCmd = "SQL to Execute"; conn = new SqlClient.SqlConnection("Connection to DB1"); conn.Open() objCmd = new SqlClient.SqlCommand(strCmd, conn); objCmd.ExecuteNonQuery(); string strCmd2 = "SQL to Execute"; conn2 = new SqlClient.SqlConnection("Connection to DB2"); conn2.Open() objCmd2 = new SqlClient.SqlCommand(strCmd2, conn2); objCmd2.ExecuteNonQuery(); }
3- با DataProviderهای متفاوت نظیر Oracle و OleDb و ODBC سازگار است.
4- از تراکنشهای تو در تو به خوبی پشتیبانی میکنه(Nested Transaction)
using(TransactionScope scope1 = new TransactionScope()) { using(TransactionScope scope2 = new TransactionScope(TransactionScopeOption.Required)) { ... } using(TransactionScope scope3 = new TransactionScope(TransactionScopeOption.RequiresNew)) { ... } using(TransactionScope scope4 = new TransactionScope(TransactionScopeOption.Suppress)) { ... } }
*استفاده از این روش در سیستم هایی که تعداد کاربران آنلاین آن زیاد است و هم چنین تعداد تراکنشهای موجود نیز در سطح سیستم خیلی زیاد باشه مناسب نیست.
*تراکنشهای استفاده شده از این روش کند هستند.(مخصوصا که تراکنش در سطح دیتابیس با تعداد و حجم داده زیاد باشه)
امکان تغییر IsolationLevel در طی انجام یک تراکنش امکان پذیر نیست.
(به شخصه مواد * رو در سطح یک پروژه با شرایط کاربران و حجم داده زیاد تست کردم و نتیجه مطلوب حاصل نشد)
موفق باشید.