[MaxLength(2), Required(ErrorMessage = "طول فیلد بیش از حد مجاز است")] public string ProductName { get; set; }
من از MySQL استفاده کردم. میتونه مشکل از این باشه؟
[MaxLength(2), Required(ErrorMessage = "طول فیلد بیش از حد مجاز است")] public string ProductName { get; set; }
using System;
using System.Collections.Generic;
namespace Refactoring.Day7.IntroduceParameterObject.Before
{
public class Registration
{
public void Create(string name, DateTime date, DateTime validUntil,
IEnumerable<string> courses, decimal credits)
{
// do work
}
}
}
using System;
using System.Collections.Generic;
namespace Refactoring.Day7.IntroduceParameterObject.After
{
public class RegistrationContext
{
public string Name {set;get;}
public DateTime Date {set;get;}
public DateTime ValidUntil {set;get;}
public IEnumerable<string> Courses {set;get;}
public decimal Credits { set; get; }
}
}
namespace Refactoring.Day7.IntroduceParameterObject.After
{
public class Registration
{
public void Create(RegistrationContext registrationContext)
{
// do work
}
}
}
public int GetIndex(int pageSize, int pageNumber, ...) { ...
سناریویی را فرض کنید که در آن برای انجام عملیات CRUD از یک سرویس Web API استفاده میشود. همچنین مدیریت دادهها با مدل Code-First پیاده سازی شده است. در مثال جاری یک کلاینت Console Application خواهیم داشت که یک سرویس Web API را فراخوانی میکند. توجه داشته باشید که هر اپلیکیشن در Solution مجزایی قرار دارد. تفکیک پروژهها برای شبیه سازی یک محیط n-Tier انجام شده است.
فرض کنید مدلی مانند تصویر زیر داریم.
همانطور که میبینید مدل جاری، سفارشات یک اپلیکیشن فرضی را معرفی میکند. میخواهیم مدل و کد دسترسی به دادهها را در یک سرویس Web API پیاده سازی کنیم، تا هر کلاینتی که از HTTP استفاده میکند بتواند عملیات CRUD را انجام دهد. برای ساختن سرویس مورد نظر مراحل زیر را دنبال کنید.
public class Order { public int OrderId { get; set; } public string Product { get; set; } public int Quantity { get; set; } public string Status { get; set; } public byte[] TimeStamp { get; set; } }
public class Recipe1Context : DbContext { public Recipe1Context() : base("Recipe1ConnectionString") { } public DbSet<Order> Orders { get; set; } protected override void OnModelCreating(DbModelBuilder modelBuilder) { modelBuilder.Entity<Order>().ToTable("Orders"); // Following configuration enables timestamp to be concurrency token modelBuilder.Entity<Order>().Property(x => x.TimeStamp) .IsConcurrencyToken() .HasDatabaseGeneratedOption(DatabaseGeneratedOption.Computed); } }
<connectionStrings> <add name="Recipe1ConnectionString" connectionString="Data Source=.; Initial Catalog=EFRecipes; Integrated Security=True; MultipleActiveResultSets=True" providerName="System.Data.SqlClient" /> </connectionStrings>
protected void Application_Start() { // Disable Entity Framework Model Compatibilty Database.SetInitializer<Recipe1Context>(null); ... }
public class OrderController : ApiController { // GET api/order public IEnumerable<Order> Get() { using (var context = new Recipe1Context()) { return context.Orders.ToList(); } } // GET api/order/5 public Order Get(int id) { using (var context = new Recipe1Context()) { return context.Orders.FirstOrDefault(x => x.OrderId == id); } } // POST api/order public HttpResponseMessage Post(Order order) { // Cleanup data from previous requests Cleanup(); using (var context = new Recipe1Context()) { context.Orders.Add(order); context.SaveChanges(); // create HttpResponseMessage to wrap result, assigning Http Status code of 201, // which informs client that resource created successfully var response = Request.CreateResponse(HttpStatusCode.Created, order); // add location of newly-created resource to response header response.Headers.Location = new Uri(Url.Link("DefaultApi", new { id = order.OrderId })); return response; } } // PUT api/order/5 public HttpResponseMessage Put(Order order) { using (var context = new Recipe1Context()) { context.Entry(order).State = EntityState.Modified; context.SaveChanges(); // return Http Status code of 200, informing client that resouce updated successfully return Request.CreateResponse(HttpStatusCode.OK, order); } } // DELETE api/order/5 public HttpResponseMessage Delete(int id) { using (var context = new Recipe1Context()) { var order = context.Orders.FirstOrDefault(x => x.OrderId == id); context.Orders.Remove(order); context.SaveChanges(); // Return Http Status code of 200, informing client that resouce removed successfully return Request.CreateResponse(HttpStatusCode.OK); } } private void Cleanup() { using (var context = new Recipe1Context()) { context.Database.ExecuteSqlCommand("delete from [orders]"); } } }
نکته: قسمت هایی از اپلیکیشن که باید در لایههای مختلف مورد استفاده قرار گیرند - مانند کلاسهای موجودیتها - بهتر است در لایه مجزایی قرار داده شده و به اشتراک گذاشته شوند. مثلا میتوانید پروژه ای از نوع Class Library بسازید و تمام موجودیتها را در آن تعریف کنید. سپس لایههای مختلف این پروژه را ارجاع خواهند کرد.
فایل program.cs را باز کنید و کد زیر را به آن اضافه نمایید.
private HttpClient _client; private Order _order; private static void Main() { Task t = Run(); t.Wait(); Console.WriteLine("\nPress <enter> to continue..."); Console.ReadLine(); } private static async Task Run() { // create instance of the program class var program = new Program(); program.ServiceSetup(); program.CreateOrder(); // do not proceed until order is added await program.PostOrderAsync(); program.ChangeOrder(); // do not proceed until order is changed await program.PutOrderAsync(); // do not proceed until order is removed await program.RemoveOrderAsync(); } private void ServiceSetup() { // map URL for Web API cal _client = new HttpClient { BaseAddress = new Uri("http://localhost:3237/") }; // add Accept Header to request Web API content // negotiation to return resource in JSON format _client.DefaultRequestHeaders.Accept. Add(new MediaTypeWithQualityHeaderValue("application/json")); } private void CreateOrder() { // Create new order _order = new Order { Product = "Camping Tent", Quantity = 3, Status = "Received" }; } private async Task PostOrderAsync() { // leverage Web API client side API to call service var response = await _client.PostAsJsonAsync("api/order", _order); Uri newOrderUri; if (response.IsSuccessStatusCode) { // Capture Uri of new resource newOrderUri = response.Headers.Location; // capture newly-created order returned from service, // which will now include the database-generated Id value _order = await response.Content.ReadAsAsync<Order>(); Console.WriteLine("Successfully created order. Here is URL to new resource: {0}", newOrderUri); } else Console.WriteLine("{0} ({1})", (int)response.StatusCode, response.ReasonPhrase); } private void ChangeOrder() { // update order _order.Quantity = 10; } private async Task PutOrderAsync() { // construct call to generate HttpPut verb and dispatch // to corresponding Put method in the Web API Service var response = await _client.PutAsJsonAsync("api/order", _order); if (response.IsSuccessStatusCode) { // capture updated order returned from service, which will include new quanity _order = await response.Content.ReadAsAsync<Order>(); Console.WriteLine("Successfully updated order: {0}", response.StatusCode); } else Console.WriteLine("{0} ({1})", (int)response.StatusCode, response.ReasonPhrase); } private async Task RemoveOrderAsync() { // remove order var uri = "api/order/" + _order.OrderId; var response = await _client.DeleteAsync(uri); if (response.IsSuccessStatusCode) Console.WriteLine("Sucessfully deleted order: {0}", response.StatusCode); else Console.WriteLine("{0} ({1})", (int)response.StatusCode, response.ReasonPhrase); }
با اجرای اپلیکیشن Web API شروع کنید. این اپلیکیشن یک کنترلر Web API دارد که پس از اجرا شما را به صفحه خانه هدایت میکند. در این مرحله اپلیکیشن در حال اجرا است و سرویسهای ما قابل دسترسی هستند.
حال اپلیکیشن کنسول را باز کنید. روی خط اول کد program.cs یک breakpoint تعریف کرده و اپلیکیشن را اجرا کنید. ابتدا آدرس سرویس Web API را پیکربندی کرده و خاصیت Accept Header را مقدار دهی میکنیم. با این کار از سرویس مورد نظر درخواست میکنیم که دادهها را با فرمت JSON بازگرداند. سپس یک آبجکت Order میسازیم و با فراخوانی متد PostAsJsonAsync آن را به سرویس ارسال میکنیم. این متد روی آبجکت HttpClient تعریف شده است. اگر به اکشن متد Post در کنترلر Order یک breakpoint اضافه کنید، خواهید دید که این متد سفارش جدید را بعنوان یک پارامتر دریافت میکند و آن را به لیست موجودیتها در Context جاری اضافه مینماید. این عمل باعث میشود که آبجکت جدید بعنوان Added علامت گذاری شود، در این مرحله Context جاری شروع به ردیابی تغییرات میکند. در آخر با فراخوانی متد SaveChanges دادهها را ذخیره میکنیم. در قدم بعدی کد وضعیت 201 (Created) و آدرس منبع جدید را در یک آبجکت HttpResponseMessage قرار میدهیم و به کلاینت ارسال میکنیم. هنگام استفاده از Web API باید اطمینان حاصل کنیم که کلاینتها درخواستهای ایجاد رکورد جدید را بصورت POST ارسال میکنند. درخواستهای HTTP Post بصورت خودکار به اکشن متد متناظر نگاشت میشوند.
در مرحله بعد عملیات بعدی را اجرا میکنیم، تعداد سفارش را تغییر میدهیم و موجودیت جاری را با فراخوانی متد PutAsJsonAsync به سرویس Web API ارسال میکنیم. اگر به اکشن متد Put در کنترلر سرویس یک breakpoint اضافه کنید، خواهید دید که آبجکت سفارش بصورت یک پارامتر دریافت میشود. سپس با فراخوانی متد Entry و پاس دادن موجودیت جاری بعنوان رفرنس، خاصیت State را به Modified تغییر میدهیم، که این کار موجودیت را به Context جاری میچسباند. حال فراخوانی متد SaveChanges یک اسکریپت بروز رسانی تولید خواهد کرد. در مثال جاری تمام فیلدهای آبجکت Order را بروز رسانی میکنیم. در شمارههای بعدی این سری از مقالات، خواهیم دید چگونه میتوان تنها فیلدهایی را بروز رسانی کرد که تغییر کرده اند. در آخر عملیات را با بازگرداندن کد وضعیت 200 (OK) به اتمام میرسانیم.
در مرحله بعد، عملیات نهایی را اجرا میکنیم که موجودیت Order را از منبع داده حذف میکند. برای اینکار شناسه (Id) رکورد مورد نظر را به آدرس سرویس اضافه میکنیم و متد DeleteAsync را فراخوانی میکنیم. در سرویس Web API رکورد مورد نظر را از دیتابیس دریافت کرده و متد Remove را روی Context جاری فراخوانی میکنیم. این کار موجودیت مورد نظر را بعنوان Deleted علامت گذاری میکند. فراخوانی متد SaveChanges یک اسکریپت Delete تولید خواهد کرد که نهایتا منجر به حذف شدن رکورد میشود.
در یک اپلیکیشن واقعی بهتر است کد دسترسی دادهها از سرویس Web API تفکیک شود و در لایه مجزایی قرار گیرد.
using System.Collections.Generic; using System.Text.Json; namespace JsonTests { public class Product { public int Id { get; set; } public string Name { get; set; } public bool IsInStock { get; set; } } class Program { static void Main(string[] args) { var products = JsonSerializer.Deserialize<List<Product>>("[{\"Id\":1026,\"Name\":\"P1\",\"IsInStock\":\"false\"}]"); } } }
An unhandled exception of type 'System.Text.Json.JsonException' occurred in System.Text.Json.dll Inner exceptions found, see $exception in variables window for more details. Innermost exception System.InvalidOperationException : Cannot get the value of a token type 'String' as a boolean.
public class BooleanConverter : JsonConverter<bool> { public override bool Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { var value = reader.GetString(); if (value.Equals("true", StringComparison.OrdinalIgnoreCase) || value.Equals("yes", StringComparison.OrdinalIgnoreCase) || value.Equals("1", StringComparison.Ordinal)) { return true; } if (value.Equals("false", StringComparison.OrdinalIgnoreCase) || value.Equals("no", StringComparison.OrdinalIgnoreCase) || value.Equals("0", StringComparison.Ordinal)) { return false; } throw new NotSupportedException($"`{value}` can't be converted to `bool`."); } public override void Write(Utf8JsonWriter writer, bool value, JsonSerializerOptions options) { switch (value) { case true: writer.WriteStringValue("true"); break; case false: writer.WriteStringValue("false"); break; } } }
public abstract class JsonConverter<T> : JsonConverter { protected internal JsonConverter(); public override bool CanConvert(Type typeToConvert); public abstract T Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options); public abstract void Write(Utf8JsonWriter writer, T value, JsonSerializerOptions options); }
var options = new JsonSerializerOptions(); options.Converters.Add(new BooleanConverter()); var products = JsonSerializer.Deserialize<List<Product>>( "[{\"Id\":1026,\"Name\":\"P1\",\"IsInStock\":\"false\"}]", options);
[JsonConverter(typeof(BooleanConverter))] public bool IsInStock { get; set; }
var options = new JsonSerializerOptions() {WriteIndented = true }; options.Converters.Add(new BooleanConverter()); var data = JsonSerializer.Serialize<List<Product>>(productList, options);
public class Rpt { public string Title {set;get;} public int Price {set;get;} }
var list = new List<Rpt>(); list.Add(new Rpt{ Title = "عنوان دلخواه", Price = 1});
var mockIdentityVerifier = new Mock<IIdentityVerifier>(MockBehavior.Strict);
Test method Loans.Tests.LoanApplicationProcessorShould.Accept threw exception: Moq.MockException: IIdentityVerifier.Initialize() invocation failed with mock behavior Strict. All invocations on the mock must have a corresponding setup.
mockIdentityVerifier.Setup(x => x.Initialize());
try { _creditScorer.CalculateScore(application.Applicant.Name, application.Applicant.Address); } catch { return application.IsAccepted; }
mockCreditScorer.Setup(x => x.CalculateScore(It.IsAny<string>(), It.IsAny<string>())) .Throws(new InvalidOperationException("Test Exception"));
Assert.IsFalse(application.IsAccepted);
using System; namespace Loans.Models { public class CreditScoreResultArgs : EventArgs { public int Score { get; set; } } }
public interface ICreditScorer { event EventHandler<CreditScoreResultArgs> ResultAvailable;
mockCreditScorer.Raise(x => x.ResultAvailable += null, new CreditScoreResultArgs());
mockCreditScorer.Setup(x => x.CalculateScore(It.IsAny<string>(), It.IsAny<string>())) .Raises(x => x.ResultAvailable += null, new CreditScoreResultArgs());
namespace Loans.Tests { [TestClass] public class LoanApplicationProcessorShould { [TestMethod] public void AcceptUsingPartialMock() { var product = new LoanProduct {Id = 99, ProductName = "Loan", InterestRate = 5.25m}; var amount = new LoanAmount {CurrencyCode = "Rial", Principal = 2_000_000_0}; var applicant = new Applicant {Id = 1, Name = "User 1", Age = 25, Address = "This place", Salary = 1_500_000_0}; var application = new LoanApplication {Id = 42, Product = product, Amount = amount, Applicant = applicant}; var mockIdentityVerifier = new Mock<IdentityVerifierServiceGateway>(); mockIdentityVerifier.Setup(x => x.CallService(applicant.Name, applicant.Age, applicant.Address)) .Returns(true); var mockCreditScorer = new Mock<ICreditScorer>(); mockCreditScorer.Setup(x => x.ScoreResult.ScoreValue.Score).Returns(110_000); var sut = new LoanApplicationProcessor(mockIdentityVerifier.Object, mockCreditScorer.Object); sut.Process(application); Assert.IsTrue(application.IsAccepted); } } }
public virtual bool CallService(string applicantName, int applicantAge, string applicantAddress)
public bool Validate(string applicantName, int applicantAge, string applicantAddress) { Connect(); var isValidIdentity = CallService(applicantName, applicantAge, applicantAddress); LastCheckTime = DateTime.Now; Disconnect(); return isValidIdentity; }
public bool Validate(string applicantName, int applicantAge, string applicantAddress) { Connect(); var isValidIdentity = CallService(applicantName, applicantAge, applicantAddress); LastCheckTime = GetCurrentTime(); Disconnect(); return isValidIdentity; } public virtual DateTime GetCurrentTime() { return DateTime.Now; }
var expectedTime = new DateTime(2000, 1, 1); mockIdentityVerifier.Setup(x => x.GetCurrentTime()) .Returns(expectedTime); // ... Assert.AreEqual(expectedTime, mockIdentityVerifier.Object.LastCheckTime);
mockIdentityVerifier.Protected().Setup<bool>( "CallService",applicant.Name, applicant.Age, applicant.Address) .Returns(true); var expectedTime = new DateTime(2000, 1, 1); mockIdentityVerifier.Protected().Setup<DateTime>("GetCurrentTime") .Returns(expectedTime);
interface IIdentityVerifierServiceGatewayProtectedMembers { DateTime GetCurrentTime(); bool CallService(string applicantName, int applicantAge, string applicantAddress); }
mockIdentityVerifier.Protected() .As<IIdentityVerifierServiceGatewayProtectedMembers>() .Setup(x => x.CallService(It.IsAny<string>(), It.IsAny<int>(), It.IsAny<string>())) .Returns(true); var expectedTime = new DateTime(2000, 1, 1); mockIdentityVerifier.Protected() .As<IIdentityVerifierServiceGatewayProtectedMembers>() .Setup(x => x.GetCurrentTime()) .Returns(expectedTime);
using System; namespace Loans.Services.Contracts { public interface INowProvider { DateTime GetNow(); } }
public class IdentityVerifierServiceGateway : IIdentityVerifier { private readonly INowProvider _nowProvider; public DateTime LastCheckTime { get; private set; } public IdentityVerifierServiceGateway(INowProvider nowProvider) { _nowProvider = nowProvider; }
public bool Validate(string applicantName, int applicantAge, string applicantAddress) { Connect(); var isValidIdentity = CallService(applicantName, applicantAge, applicantAddress); LastCheckTime = _nowProvider.GetNow(); // ...
var mockNowProvider = new Mock<INowProvider>(); mockNowProvider.Setup(x => x.GetNow()).Returns(expectedTime); var mockIdentityVerifier = new Mock<IdentityVerifierServiceGateway>(mockNowProvider.Object);
[DataContract] public class ApplicationContext { [DataMember( IsRequired = true )] public string UserId { get { return _userId; } set { _userId = value; } } private string _userId; [DataMember( IsRequired = true )] public static ApplicationContext Current { get { return _current; } private set { _current = value; } } private static ApplicationContext _current;
public static void Register( ApplicationContext appContext ) { Current = appContext; IsRegistered = true; } }
public class ClientMessageHeaderInspector<T> : IClientMessageInspector { private readonly T _vaccine; public ClientMessageHeaderInspector( T vaccine ) { this._vaccine = vaccine; } public void AfterReceiveReply( ref Message reply, object correlationState ) { } public object BeforeSendRequest( ref Message request, IClientChannel channel ) { MessageHeader messageHeader = MessageHeader.CreateHeader( typeof( T ).Name, typeof( T ).Namespace, this._vaccine ); request.Headers.Add( messageHeader ); return null; } }
public class ApplicationContextMessageBehavior : IEndpointBehavior { ClientMessageHeaderInspector<ApplicationContext> inspector = null; public ApplicationContextMessageBehavior() { inspector = new ClientMessageHeaderInspector<ApplicationContext>( ApplicationContext.Current ); } public void AddBindingParameters( ServiceEndpoint endpoint, BindingParameterCollection bindingParameters ) { } public void ApplyClientBehavior( ServiceEndpoint endpoint, ClientRuntime clientRuntime ) { clientRuntime.MessageInspectors.Add( inspector ); } public void ApplyDispatchBehavior( ServiceEndpoint endpoint, EndpointDispatcher endpointDispatcher ) { } public void Validate( ServiceEndpoint endpoint ) { } }
public class ServiceMapper<TChannel> { internal static EndpointAddress EPAddress { get { return _epAddress; } } private static EndpointAddress _epAddress; public static TChannel CreateChannel( Binding binding, string uriBase, string serviceName, bool setCredential ) { _epAddress = new EndpointAddress( String.Format( "{0}{1}", uriBase, serviceName ) ); var factory = new ChannelFactory<TChannel>( binding, _epAddress ); ApplicationContext.Register( new ApplicationContext { UserId = Guid.NewGuid() } );
factory.Endpoint.Behaviors.Add( new ApplicationContextMessageBehavior() ); TChannel proxy = factory.CreateChannel(); if ( factory.Endpoint.Behaviors.OfType<ApplicationContextMessageBehavior>().Any() ) { using ( var scope = new OperationContextScope( ( IClientChannel )proxy ) ) { OperationContext.Current.OutgoingMessageHeaders.Add( MessageHeader.CreateHeader( typeof( ApplicationContext ).Name, typeof( ApplicationContext ).Namespace, ApplicationContext.Current ) ); } } return proxy; }
OperationContext.Current.OutgoingMessageHeaders.Add( MessageHeader.CreateHeader( typeof( ApplicationContext ).Name, typeof( ApplicationContext ).Namespace, AppConfiguration.Application ) );
if ( OperationContext.Current != null && OperationContext.Current.IncomingMessageHeaders.FindHeader( typeof( ApplicationContext ).Name , typeof( ApplicationContext ).Namespace ) > 0 ) { _application = OperationContext.Current.IncomingMessageHeaders.GetHeader<ApplicationContext>( typeof( ApplicationContext ).Name , typeof( ApplicationContext ).Namespace ); }
public class Data { public string Name { get; set; } public string ValueInHex { get; set; } }
در اینجا مقدار Hex برایمان قابل فهم نیست. سناریویی را در نظر بگیرید که مقادیر باید داخل دیتابیس به صورت Hex نگهداری شوند، اما میخواهیم هنگام دیباگ، مقدار پراپرتی HexValue به صورت قابل درک و decimal آن نمایش داده شود.
برای انجام اینکار میتوانیم از DebuggerTypeProxy استفاده کنیم. ابتدا کلاسی ایجاد میکنیم که بعنوان proxy، مقادیر را به شکلی که نیاز داریم نمایش دهد. این کلاس object اصلی را در Constructor دریافت کرده و مقادیر مورد نظرمان، از طریق property هایی که در آن تعریف میکنیم قابل دسترسی هستند:
public class DataDebugView { private readonly Data _data; public DataDebugView(Data data) { _data = data; } public string DecimalValue { get { bool isValidHex = int.TryParse(_data.HexValue, System.Globalization.NumberStyles.HexNumber, null, out var value); return isValidHex ? value.ToString() : "INVALID HEX STRING"; } } }
در نهایت برای اعمال کردن این کلاس proxy، از ویژگی DebuggerTypeProxy بر روی کلاس اصلی استفاده میکنیم:
[DebuggerTypeProxy(typeof(DataDebugView))] public class Data { public string Name { get; set; } public string HexValue { get; set; } }
بعد از اعمال تغییرات و اجرای دوباره برنامه، نحوه نمایش مقادیر کلاس به این صورت تغییر خواهند یافت:
public static string GetSignalRContent() { var resolver = new DefaultHubManager(new DefaultDependencyResolver()); var proxy = new DefaultJavaScriptProxyGenerator(resolver, new NullJavaScriptMinifier()); return proxy.GenerateProxy("/signalr"); }
public class CustomVirtualPathProvider : VirtualPathProvider { public CustomActionVirtualPathProvider(VirtualPathProvider virtualPathProvider) { // Wrap an existing virtual path provider VirtualPathProvider = virtualPathProvider; } protected VirtualPathProvider VirtualPathProvider { get; set; } public override string CombineVirtualPaths(string basePath, string relativePath) { return VirtualPathProvider.CombineVirtualPaths(basePath, relativePath); } public override bool DirectoryExists(string virtualDir) { return VirtualPathProvider.DirectoryExists(virtualDir); } public override bool FileExists(string virtualPath) { if (virtualPath == "~/signalr/hubs") { return true; } return VirtualPathProvider.FileExists(virtualPath); } public override CacheDependency GetCacheDependency(string virtualPath, IEnumerable virtualPathDependencies, DateTime utcStart) { // BaseClass can't create a CacheDependency for your content, remove it // You could also add your own CacheDependency and aggregate it with the base dependency List<string> virtualPathDependenciesCopy = virtualPathDependencies.Cast<string>().ToList(); virtualPathDependenciesCopy.Remove("~/signalr/hubs"); return VirtualPathProvider.GetCacheDependency(virtualPath, virtualPathDependenciesCopy, utcStart); } public override string GetCacheKey(string virtualPath) { return VirtualPathProvider.GetCacheKey(virtualPath); } public override VirtualDirectory GetDirectory(string virtualDir) { return VirtualPathProvider.GetDirectory(virtualDir); } public override VirtualFile GetFile(string virtualPath) { if (virtualPath == "~/signalr/hubs") { return new CustomVirtualFile(virtualPath, new MemoryStream(Encoding.Default.GetBytes(GetSignalRContent()))); } return VirtualPathProvider.GetFile(virtualPath); } public override string GetFileHash(string virtualPath, IEnumerable virtualPathDependencies) { return VirtualPathProvider.GetFileHash(virtualPath, virtualPathDependencies); } public override object InitializeLifetimeService() { return VirtualPathProvider.InitializeLifetimeService(); } } public class CustomVirtualFile : VirtualFile { public CustomVirtualFile (string virtualPath, Stream stream) : base(virtualPath) { Stream = stream; } public Stream Stream { get; private set; } public override Stream Open() { return Stream; } }
public override bool FileExists(string virtualPath) { if (virtualPath == "~/signalr/hubs") { return true; } return VirtualPathProvider.FileExists(virtualPath); }
public override CacheDependency GetCacheDependency(string virtualPath, IEnumerable virtualPathDependencies, DateTime utcStart) { List<string> virtualPathDependenciesCopy = virtualPathDependencies.Cast<string>().ToList(); virtualPathDependenciesCopy.Remove("~/signalr/hubs"); return VirtualPathProvider.GetCacheDependency(virtualPath, virtualPathDependenciesCopy, utcStart); }
public override VirtualFile GetFile(string virtualPath) { if (virtualPath == "~/signalr/hubs") { return new CustomVirtualFile(virtualPath, new MemoryStream(Encoding.Default.GetBytes(GetSignalRContent()))); } return VirtualPathProvider.GetFile(virtualPath); }
public static void RegisterBundles(BundleCollection bundles) { BundleTable.VirtualPathProvider = new CustomVirtualPathProvider(BundleTable.VirtualPathProvider); Bundle include = new Bundle("~/bundle") .Include("~/Content/static.js") .Include("~/signalr/hubs"); bundles.Add(include); }