فایل global.json در VS 2017 و ساختار جدید آن، از پروژههای ASP.NET Core حذف شدهاست. جایگزین آن مراجعه به برگهی خواص پروژه و انتخاب شماره فریم ورک مدنظر است:
syntax = "proto3"; package helloworld; service Greeter { rpc SayHello (HelloRequest) returns (HelloReply) {} } message HelloRequest { string name = 1; } message HelloReply { string message = 1; }
Google.Protobuf grpc Grpc.Tools
<ItemGroup> <Protobuf Include="helloworld.proto" /> </ItemGroup>
using System; using System.Collections.Generic; using System.Threading.Tasks; using Helloworld; using Grpc.Core; namespace ServerGrpc { class GreeterImpl : Greeter.GreeterBase { public override Task<HelloReply> SayHello(HelloRequest request, ServerCallContext context) { System.Console.WriteLine("request made!"); return Task.FromResult(new HelloReply { Message = "Hello " + request.Name }); } } class Program { const int Port = 50051; public static void Main(string[] args) { Server server = new Server { Services = { Greeter.BindService(new GreeterImpl()) }, Ports = { new ServerPort("localhost", Port, ServerCredentials.Insecure) } }; server.Start(); Console.WriteLine("Greeter server listening on port " + Port); Console.WriteLine("Press any key to stop the server..."); Console.ReadKey(); server.ShutdownAsync().Wait(); } } }
obj/Debug/netcoreapp2.2(یا نسخهی دیگری که استفاده میکنید)
protoc helloworld.proto --go_out=plugins=grpc:.
package main import ( "fmt" "golang.org/x/net/context" "google.golang.org/grpc" "gosample/proto" ) func main() { initial() } func initial(){ conn, _ := grpc.Dial("localhost:50051", grpc.WithInsecure()) defer conn.Close() client := helloworld.NewGreeterClient(conn) data, _ := client.SayHello(context.Background(), &helloworld.HelloRequest{Name : "Ali"}) fmt.Println(data) }
namespace PluginsBase { public interface IPlugin { string Name { get; } void Run(); } }
using PluginsBase; namespace Plugin1 { public class Plugin1Main : IPlugin { public string Name { get { return "Test 1"; } } public void Run() { // todo: ... } } }
Copy "$(ProjectDir)$(OutDir)$(TargetName).*" "$(SolutionDir)WinFormsWithPluginSupport\bin\debug\Plugins"
Copy "$(ProjectDir)$(OutDir)*.*" "$(SolutionDir)WinFormsWithPluginSupport\bin\debug\Plugins"
PM> install-package structuremap
using System.IO; using System.Windows.Forms; using PluginsBase; using StructureMap.Configuration.DSL; using StructureMap.Graph; namespace WinFormsWithPluginSupport.Core { public class PluginsRegistry : Registry { public PluginsRegistry() { this.Scan(scanner => { scanner.AssembliesFromPath( path: Path.Combine(Application.StartupPath, "plugins"), // یک اسمبلی نباید دوبار بارگذاری شود assemblyFilter: assembly => { return !assembly.FullName.Equals(typeof(IPlugin).Assembly.FullName); }); scanner.AddAllTypesOf<IPlugin>().NameBy(item => item.FullName); }); } } }
using System; using System.Threading; using StructureMap; namespace WinFormsWithPluginSupport { public static class IocConfig { private static readonly Lazy<Container> _containerBuilder = new Lazy<Container>(defaultContainer, LazyThreadSafetyMode.ExecutionAndPublication); public static IContainer Container { get { return _containerBuilder.Value; } } private static Container defaultContainer() { return new Container(x => { x.AddRegistry<PluginsRegistry>(); }); } } }
using System.Windows.Forms; using StructureMap; namespace WinFormsWithPluginSupport { public partial class FrmMain : Form { private readonly IContainer _container; public FrmMain(IContainer container) { _container = container; InitializeComponent(); } } }
using System; using System.Windows.Forms; namespace WinFormsWithPluginSupport { static class Program { [STAThread] static void Main() { Application.EnableVisualStyles(); Application.SetCompatibleTextRenderingDefault(false); Application.Run(IocConfig.Container.GetInstance<FrmMain>()); } } }
using System.Linq; using System.Windows.Forms; using PluginsBase; using StructureMap; using WinFormsWithPluginSupport.Core; namespace WinFormsWithPluginSupport { public partial class FrmMain : Form { private readonly IContainer _container; public FrmMain(IContainer container) { _container = container; InitializeComponent(); } private void BtnRun_Click(object sender, System.EventArgs e) { var plugins = _container.GetAllInstances<IPlugin>().ToList(); foreach (var plugin in plugins) { plugin.Run(); } } private void BtnReload_Click(object sender, System.EventArgs e) { _container.EjectAllInstancesOf<IPlugin>(); _container.Configure(x => x.AddRegistry<PluginsRegistry>() ); } } }
using System.ComponentModel.DataAnnotations; namespace Models; public record OrderPlace { public Address BillingAddress { get; set; } = new(); public Address ShippingAddress { get; set; } = new(); } public class Address { [Required] public string Name { get; set; } = default!; public string? AddressLine1 { get; set; } public string? AddressLine2 { get; set; } public string? City { get; set; } [Required] public string PostCode { get; set; } = default!; }
@inherits Editor<Models.Address> <div> <label>Name</label> <InputText @bind-Value="Value.Name"/> </div> <div> <label>Address 1</label> <InputText @bind-Value="Value.AddressLine1"/> </div> <div> <label>Address 2</label> <InputText @bind-Value="Value.AddressLine2"/> </div> <div> <label>City</label> <InputText @bind-Value="Value.City"/> </div> <div> <label>Post Code</label> <InputText @bind-Value="Value.PostCode"/> </div>
@page "/checkout" @using Models @if (!_submitted && PlaceModel != null) { <EditForm Model="PlaceModel" method="post" OnValidSubmit="SubmitOrder" FormName="checkout"> <DataAnnotationsValidator/> <h4>Bill To:</h4> <AddressEntry @bind-Value="PlaceModel.BillingAddress"/> <h4>Ship To:</h4> <AddressEntry @bind-Value="PlaceModel.ShippingAddress"/> <button type="submit">Submit</button> <ValidationSummary/> </EditForm> } @if (_submitted && PlaceModel != null) { <div> <h2>Order Summary</h2> <h3>Shipping To:</h3> <dl> <dt>Name</dt> <dd>@PlaceModel.BillingAddress.Name</dd> <dt>Address 1</dt> <dd>@PlaceModel.BillingAddress.AddressLine1</dd> <dt>Address 2</dt> <dd>@PlaceModel.BillingAddress.AddressLine2</dd> <dt>City</dt> <dd>@PlaceModel.BillingAddress.City</dd> <dt>Post Code</dt> <dd>@PlaceModel.BillingAddress.PostCode</dd> </dl> </div> } @code { bool _submitted; [SupplyParameterFromForm] public OrderPlace? PlaceModel { get; set; } protected override void OnInitialized() { PlaceModel ??= GetOrderPlace(); } private void SubmitOrder() { _submitted = true; } private static OrderPlace GetOrderPlace() => new() { BillingAddress = new Address { PostCode = "12345", Name = "Test 1", }, ShippingAddress = new Address { PostCode = "67890", Name = "Test 2", }, }; }
<form method="post"> <input type="hidden" name="_handler" value="checkout" /> <input type="hidden" name="__RequestVerificationToken" value="CfDxxx" /> . . . <button type="submit">Submit</button> </form>
<EditForm Model="PlaceModel" method="post" OnValidSubmit="SubmitOrder" FormName="checkout">
[SupplyParameterFromForm] public OrderPlace? PlaceModel { get; set; }
@page "/checkout" <FormMappingScope Name="store-checkout"> <CheckoutForm /> </FormMappingScope>
<form method="post"> <input type="hidden" name="_handler" value="[store-checkout]checkout" /> <input type="hidden" name="__RequestVerificationToken" value="CfDxxxxx" /> . . . <button type="submit">Submit</button> </form>
<form method="post" @onsubmit="SaveData" @formname="MyFormName"> <AntiforgeryToken /> <InputText @bind-Value="Name" /> <button>Submit</button> </form>
@page "/" <form method="GET"> <input type="text" name="q"/> <button type="submit">Search</button> </form> @code { [SupplyParameterFromQuery(Name="q")] public string SearchTerm { get; set; } protected override async Task OnInitializedAsync() { // do something with the search term } }
<button class="nav-link border-0" @onclick="BeginSignOut">Log out</button>
<EditForm Context="ctx" FormName="LogoutForm" method="post" Model="@Foo" OnValidSubmit="BeginSignOut"> <button type="submit" class="nav-link border-0">Log out</button> </EditForm> @code{ [SupplyParameterFromForm(Name = "LogoutForm")] public string? Foo { get; set; } protected override void OnInitialized() => Foo = ""; async Task BeginSignOut() { // TODO: SignOutAsync(); // TODO: NavigateTo("/authentication/logout"); } }
<EditForm Model="PlaceModel" method="post" OnValidSubmit="SubmitOrder" FormName="checkout" Enhance>
public class Bill { public int Id { set; get; } public string Name { set; get; } public virtual ICollection<Transaction> Transactions { set; get; } } public class Transaction { public int Id { set; get; } public DateTime AddDate { set; get; } public int Amount { set; get; } [ForeignKey("BillId")] public virtual Bill Bill { set; get; } public int BillId { set; get; } }
public class MyContext : DbContext { public DbSet<Bill> Bills { get; set; } public DbSet<Transaction> Transactions { get; set; } }
public class Configuration : DbMigrationsConfiguration<MyContext> { public Configuration() { AutomaticMigrationsEnabled = true; AutomaticMigrationDataLossAllowed = true; } protected override void Seed(MyContext context) { var bill1 = new Bill { Name = "bill-1" }; context.Bills.Add(bill1); for (int i = 0; i < 11; i++) { context.Transactions.Add(new Transaction { AddDate = DateTime.Now.AddDays(-i), Amount = 1000000000 + i, Bill = bill1 }); } base.Seed(context); } }
public static class Test { public static void RunTests() { Database.SetInitializer(new MigrateDatabaseToLatestVersion<MyContext, Configuration>()); using (var context = new MyContext()) { var sum = context.Transactions.Sum(x => x.Amount); Console.WriteLine(sum); } } }
SELECT [GroupBy1].[A1] AS [C1] FROM ( SELECT SUM([Extent1].[Amount]) AS [A1] FROM [dbo].[Transactions] AS [Extent1] ) AS [GroupBy1]
Arithmetic overflow error converting expression to data type int.
var sum2 = context.Transactions.Sum(x => (Int64)x.Amount);
SELECT [GroupBy1].[A1] AS [C1] FROM ( SELECT SUM( CAST( [Extent1].[Amount] AS bigint)) AS [A1] FROM [dbo].[Transactions] AS [Extent1] ) AS [GroupBy1]
var date = DateTime.Now.AddDays(10); var sum3 = context.Transactions .Where(x => x.AddDate > date) .Sum(x => (Int64)x.Amount);
SELECT [GroupBy1].[A1] AS [C1] FROM ( SELECT SUM( CAST( [Extent1].[Amount] AS bigint)) AS [A1] FROM [dbo].[Transactions] AS [Extent1] WHERE [Extent1].[AddDate] > @p__linq__0 ) AS [GroupBy1]
The cast to value type 'Int64' failed because the materialized value is null. Either the result type's generic parameter or the query must use a nullable type.
var date = DateTime.Now.AddDays(10); var sum3 = context.Transactions .Where(x => x.AddDate > date) .Sum(x => (Int64?)x.Amount) ?? 0;
var sum4 = context.Bills.First().Transactions.Sum(x => (Int64?)x.Amount) ?? 0;
SELECT [Extent1].[Id] AS [Id], [Extent1].[AddDate] AS [AddDate], [Extent1].[Amount] AS [Amount], [Extent1].[BillId] AS [BillId] FROM [dbo].[Transactions] AS [Extent1] WHERE [Extent1].[BillId] = @EntityKeyValue1
var entry = context.Bills.First(); var sum5 = context.Entry(entry).Collection(x => x.Transactions).Query().Sum(x => (Int64?)x.Amount) ?? 0;
SELECT [GroupBy1].[A1] AS [C1] FROM ( SELECT SUM( CAST( [Extent1].[Amount] AS bigint)) AS [A1] FROM [dbo].[Transactions] AS [Extent1] WHERE [Extent1].[BillId] = @EntityKeyValue1 ) AS [GroupBy1]
enable-migrations -ContextTypeName dbContextName1 -MigrationDirectory DataContexts\Name1Migrations
add-migration -ConfigurationTypeName FullNameSpaceCtx1.Configuration "InitialCreate"
update-database -ConfigurationTypeName FullNameSpaceCtx1.Configuration
public class Ctx1 : DbContext { public Ctx1() : base("DefaultConnection") { //Database.Log = sql => Debug.Write(sql); } } public class Ctx2 : DbContext { public Ctx2() : base("DefaultConnection") { //Database.Log = sql => Debug.Write(sql); } }
<connectionStrings> <add name="DefaultConnection" connectionString="…." providerName="System.Data.SqlClient" /> </connectionStrings>
public class Ctx2 : DbContext { public Ctx2() : base("DefaultConnection") { //Database.Log = sql => Debug.Write(sql); } protected override void OnModelCreating(DbModelBuilder modelBuilder) { modelBuilder.HasDefaultSchema("sales"); base.OnModelCreating(modelBuilder); } }
یک مثال از عبارات با قاعده:
type PhoneRegex = Regex< @"(?<AreaCode>^\d{3})-(?<PhoneNumber>\d{3}-\d{4}$)"> PhoneRegex.IsMatch "425-123-2345" |> should equal true PhoneRegex().Match("425-123-2345").CompleteMatch.Value |> should equal "425-123-2345" PhoneRegex().Match("425-123-2345").PhoneNumber.Value |> should equal "123-2345"
با استفاده از پنجره Package Manager Console دستور نصب زیر را اجرا کنید(آخرین نسخه این پکیج 1.8.31 و حجم آن کمتر از یک مگابایت است):
PM> Install-Package FSharpx.TypeProviders.Xaml
حال یک فایل Xaml به پروژه اضافه کنید و کدهای زیر را در آن کپی کنید:
<Window xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="WPF F# Sample By Masoud Pakdel" Height="350" Width="525"> <Grid Name="MainGrid"> <StackPanel Name="StackPanel1" Margin="50"> <Button Name="Button1">Who are you?</Button> </StackPanel> </Grid> </Window>
open System open System.Windows open System.Windows.Controls open FSharpx type MainWindow = XAML<"MainWindow.xaml"> let loadWindow() = let window = MainWindow() window.Button1.Click.Add(fun _ -> MessageBox.Show("Masoud Pakdel") |> ignore) window.Root [<STAThread>] (new Application()).Run(loadWindow()) |> ignore
[<STAThread>] (new Application()).Run(loadWindow()) |> ignore
public double TotalRating { get ; set ; } = 0.0 ; public int TotalRaters { get ; set ; } = 0 ; public double AverageRating { get ; set ; } = 0.0 ;
modelBuilder.Entity<Product>().OwnsOne(p = > p.Rating)
No Authentication
از این گزینه برای احراز هویت کاربرانی استفاده کنید که در قالب یک OWIN Tenant تعریف میشوند. برای مثال سایتی با نام Company.com داریم که برای کارمندان این سازمان از طریق company.onmicrosoft.com قابل دسترسی خواهد بود. نمیتوانید WAAD را طوری پیکربندی کنید که کاربران tenantهای دیگر نیز به اپلیکیشن شما دسترسی داشته باشند.
Domain
نام دامنهای در WAAD که میخواهید اپلیکیشن را برای آن پیکربندی کنید، مثلا company.onmicrosoft.com. اگر از custom domain ها استفاده میکنید مانند company.com بجای company.onmicrosoft.com میتوانید این اطلاعات را اینجا وارد کنید.
سطح دسترسی
اگر اپلیکیشن نیاز به کوئری گرفتن یا بروز رسانی اطلاعات پوشهها (directory information) را توسط Graph API دارد، از گزینههای Single Sign-On, Read Directory Data و یا Single Sign-On, Read and Write Directory Data استفاده کنید. در غیر اینصورت گزینه Single Sign-On را رها کنید. برای اطلاعات بیشتر به Application Access Levels و Using the Graph API to Query Windows Azure AD مراجعه کنید.
Application ID URI
بصورت پیش فرض، قالب پروژه یک شناسه application ID URI برای شما تولید میکند، که این کار با الحاق نام پروژه شما به نام دامنه WAAD صورت میگیرد. برای مثال، اگر نام پروژه Example باشد و نام دامنه contoso.onmicrosoft.com، شناسه خروجی https://contoso.onmicrosoft.com/Example میشود. اگر میخواهید بصورت دستی این فیلد را مقدار دهی کنید، گزینه More Options را انتخاب کنید. این شناسه باید با //:https شروع شود.
بصورت پیش فرض، اگر اپلیکیشنی که در WAAD تهیه و تدارک دیده شده است، شناسهای یکسان با شناسه موجود در پروژه Visual Studio داشته باشد، پروژه شما به اپلیکیشن موجود در WAAD متصل خواهد شد. اگر میخواهید تدارکات جدیدی ببینید تیک گزینه Overwrite the application entry if one with the same ID already exists را حذف کنید.
اگر تیک این گزینه حذف شده باشد، و ویژوال استودیو اپلیکیشنی با شناسهای یکسان را پیدا کند، عددی به آخر URI اضافه خواهد شد. مثلا فرض کنید نام پروژه Example است و اپلیکیشنی نیز با شناسه https://contoso.onmicrosoft.com/Example در WAAD وجود دارد. در این صورت اپلیکیشن جدیدی با شناسه ای مانند https://contoso.onmicrosoft.com/Example_ 20130619330903 ایجاد میشود.
تهیه و تدارک اپلیکیشن در WAAD
برای آنکه یک اپلیکیشن WAAD ایجاد کنید و یا پروژه را به یک اپلیکیشن موجود متصل کنید، ویژوال استودیو به اطلاعات ورود یک مدیر کل برای دامنه مورد نظر، نیاز دارد. هنگامی که در دیالوگ Configure Authentication روی OK کلیک میکنید، اطلاعات ورود یک مدیر کل از شما درخواست میشود و نهایتا هنگامیکه روی Create Project کلیک میکنید، ویژوال استودیو اپلیکیشن شما را در WAAD پیکربندی میکند.
برای اطلاعات بیشتر درباره نحوه استفاده از مدل احراز هویت Cloud - Single Organization به لینکهای زیر مراجعه فرمایید:
از این گزینه برای احراز هویت کاربرانی استفاده کنید که در WAAD tenantهای متعددی تعریف شدهاند. برای مثال، نام سایت contoso.com است و برای کارمندان دو سازمان از طریق آدرسهای contoso.onmicrosoft.com و fabrikam.onmicrosoft.com قابل دسترسی خواهد بود. نحوه پیکربندی این مدل نیز مانند قسمت قبلی است.
برای اطلاعات بیشتر درباره احراز هویت Cloud - Multi Organization به لینکهای زیر مراجعه کنید:
این گزینه را هنگامی انتخاب کنید که کاربران در (Windows Server Active Directory (AD تعریف شده اند و نمیخواهید از WAAD استفاده کنید. از این مدل برای ایجاد وب سایتهای اینترنت و اینترانت میتوانید استفاده کنید. برای یک وب سایت اینترنتی از (Active Directory Federation Services (ADFS استفاده کنید.
برای یک وب سایت اینترانتی، میتوانید کلا این گزینه را رها کنید و از Windows Authentication استفاده کنید. در صورت استفاده از گزینه Windows Authentication لازم نیست تا آدرس سند متادیتا (metadata document URL) را فراهم کنید، همچنین توجه داشته باشید که Windows Authentication امکان کوئری گرفتن از پوشهها و کنترل سطوح دسترسی در Active Directory را ندارد.
On-Premises Authority
آدرس سند متادیتا. این سند اطلاعاتی درباره مختصات Authority دارد که اپلیکیشن از آنها برای به پیش بردن روند احراز هویت و ورود به سایت استفاده میکند.
Application ID URI
یک شناسه منحصر به فرد که AD از آن برای شناسایی اپلیکیشن استفاده میکند. میتوانید این فیلد را خالی رها کنید تا ویژوال استودیو بصورت خودکار اپلیکیشنی بدین منظور بسازد.
قدمهای بعدی
در این مقاله با مدلهای مختلف احراز هویت در اپلیکیشنهای Visual Studio 2013 آشنا شدید و برخی تغییرات و امکانات جدید نیز بررسی شدند. برای اطلاعات تکمیلی به ASP.NET and Web Tools for Visual Studio 2013 Release Notes مراجعه کنید.