برای مطالعه بیشتر:
- MVC #19
- چگونه نرم افزارهای تحت وب سریعتری داشته باشیم؟ قسمت دوم
- Implementing second level caching in EF code first
- استفاده از AOP Interceptors برای حذف کدهای تکراری کش کردن اطلاعات در لایه سرویس برنامه
و ...
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);
<#@ template debug="false" hostspecific="false" language="C#" #> <#@ output extension=".txt" #>
خوب؛ برای ادامه، باید با قواعد کد نویسی در Text template آشنا شوید. جلسه بعدی قواعد کد نویسی T4 را بررسی خواهیم کرد.
برای پیاده سازی distributed tracing، میتوانیم از ابزار متن باز و محبوب Jaeger (با تلفظ یِگِر) که ابتدا توسط شرکت Uber منتشر شد، استفاده کنیم. نحوه کارکرد Jaeger بصورت زیر میباشد:
سادهترین روش برای راهاندازی Jager، استفاده از داکر ایمیج All in one که شامل ماژول های agent ، collector، query و ui است. پورت 6831 مربوط به agent و پورت 16686 مربوط به ui میباشد. برای جزئیات مربوط به ماژولهای مختلف از این لینک استفاده کنید.
docker run -d -p 6831:6831/udp -p 6832:6832/udp -p 14268:14268 -p 14250:14250 -p 16686:16686 -p 5778:5778 --name jaeger jaegertracing/all-in-one:latest
بعد از اجرای دستور بالا، اطلاعات مربوط به سرویسها و trace ها در ماژول Jager UI با آدرس http://localhost:16686 قابل مشاهده است.
جهت استفاده از Jaeger از پروژه تستی که شامل دو سرویس User و Gateway میباشد، استفاده میکنیم. در سرویس User، متد AddUser در صورت عدم وجود کاربر در دیتابیس، اطلاعات کاربر از گیتهاب را دریافت و در دیتابیس ذخیره میکند. سرویس Gateway از Ocelot برای مسیردهی درخواستها استفاده میکند. برای آشنایی با ocelot این پست را مطالعه نمایید.
public async Task<ApiResult<Models.User>> AddUserAsync(string username) { var result = new ApiResult<Models.User>(); var user = await _applicationDbContext.Users.FirstOrDefaultAsync(x => x.Login == username); if (user is null) { try { var url = string.Format(_appConfig.Github.ProfileUrl, username); var apiResult = await _httpClient.GetStringAsync(url); var userDto = JsonSerializer.Deserialize<UserDto>(apiResult); user = _mapper.Map<Models.User>(userDto); await _applicationDbContext.Users.AddAsync(user); await _applicationDbContext.SaveChangesAsync(); result.Result = user; result.Message = "User successfully Created"; return result; } catch (Exception e) { result.Message = "User not found"; return result; } } result.Message = "User already exist"; result.Result = user; return result; }
برای ثبت Trace مربوط به درخواستها در Jaeger ، بعد از نصب پکیجهای Jaeger و OpenTracing.Contrib.NetCore در هر دو سرویس، در کانفیگ هریک از سرویسها مورد زیر را اضافه میکنیم:
"JaegerConfig": { "Host": "localhost", "Port": 6831, "IsEnabled": true, "SamplingRate": 0.5 }
و برای اضافه شدن tracer به برنامه از متد الحاقی زیر استفاده میکنیم:
public static class Extensions { public static void AddJaeger(this IServiceCollection services, IConfiguration configuration) { var config = configuration.GetSection("JaegerConfig").Get<JaegerConfig>(); if (!(config?.IsEnabled ?? false)) return; if (string.IsNullOrEmpty(config?.Host)) throw new Exception("invalid JaegerConfig"); services.AddSingleton<ITracer>(serviceProvider => { string serviceName = Assembly.GetEntryAssembly()?.GetName().Name; ILoggerFactory loggerFactory = serviceProvider.GetRequiredService<ILoggerFactory>(); var sampler = new ProbabilisticSampler(config.SamplingRate); var reporter = new RemoteReporter.Builder() .WithLoggerFactory(loggerFactory) .WithSender(new UdpSender(config.Host, config.Port, 0)) .WithFlushInterval(TimeSpan.FromSeconds(15)) .WithMaxQueueSize(300) .Build(); ITracer tracer = new Tracer.Builder(serviceName) .WithLoggerFactory(loggerFactory) .WithSampler(sampler) .WithReporter(reporter) .Build(); GlobalTracer.Register(tracer); return tracer; }); services.AddOpenTracing(); } }
برای ثبت traceها استراتژیهای متفاوتی وجود دارد. در اینجا از ProbabilisticSampler استفاده شدهاست که در سازندهی آن میتوان درصد ثبت Traceها را مقدار دهی کرد. در نهایت این متد الحاقی را در Startup اضافه میکنیم:
builder.Services.AddJaeger(builder.Configuration);
بعد از اجرای پروژه و فراخوانی https://localhost:6000/gateway/Users/Add ، سرویس Gateway، درخواست را به سرویس User ارسال میکند و این سرویسها در Jaeger UI قابل مشاهده هستند.
جهت مشاهده trace ها ، سرویس مورد نظر را انتخاب و روی Find Traces کلیک کنید. با کلیک روی Trace مورد نظر، جزئیات فعالیت هایی مثل فراخوانی سرویس و مراجعه به دیتابیس قابل مشاهده است.
برای اضافه کردن لاگ سفارشی به یک span، میتوان از اینترفیس ITracer استفاده کرد:
private readonly IUserService _userService; private readonly ITracer _tracer; public UsersController(IUserService userService, ITracer tracer) { _userService = userService; _tracer = tracer; }
[HttpPost] public async Task<ActionResult> AddUser(AddUserDto model) { var actionName = ControllerContext.ActionDescriptor.DisplayName; using var scope = _tracer.BuildSpan(actionName).StartActive(true); scope.Span.Log($"Add user log username: {model.Username}"); return Ok(await _userService.AddUserAsync(model.Username)); }
کدهای مربوط به این مطلب در اینجا قابل دسترسی است.
public class EfRole : EfGenericService<Role>, IRole { public EfRole(IUnitOfWork uow) : base(uow) { } public bool IsUserInRole(string username, string roleName) { using (var context = new PublishingContext()) { var user = context.Users.Where(x => x.Username.Equals(username, StringComparison.CurrentCultureIgnoreCase)).FirstOrDefault(); var roles = from ur in user.Rolls from r in context.Rolls where ur.Id == r.Id select r.Rol; if (user != null) return roles.Any(x => x.Equals(roleName, StringComparison.CurrentCultureIgnoreCase)); else return false; } } }
<system.web>
<globalization culture="auto" uiCulture="auto" />
System.Threading.Thread.CurrentThread.CurrentCulture =
System.Globalization.CultureInfo.CreateSpecificCulture("fa");
IE -> Tools -> Intenet options -> Genarl tab -> Languages
string.Format("{0:C}", 10.5M)
namespace MvcApplication19.Models
{
public class Employee
{
public int Id { set; get; }
public string Name { set; get; }
}
}
using System.Web.Mvc;
using MvcApplication19.Models;
namespace MvcApplication19.Controllers
{
public class HomeController : Controller
{
public ActionResult Index()
{
var employee = new Employee { Name = "Name 1" };
return View(employee);
}
}
}
@model MvcApplication19.Models.Employee
@{
ViewBag.Title = "Index";
}
<h2>
Index</h2>
<fieldset>
<legend>Employee</legend>
<div class="display-label">
Name
</div>
<div class="display-field">
@Html.DisplayFor(model => model.Name)
</div>
</fieldset>
<fieldset>
<legend>Employee Info</legend>
@Html.DisplayForModel()
</fieldset>
namespace MvcApplication19.Views.Home {
public class Index_cshtml {
public static string Name {
get {
return ResourceManager.GetString("Name", resourceCulture);
}
}
}
}
@MvcApplication19.Views.Home.Index_cshtml.Name
using System.ComponentModel.DataAnnotations;
namespace MvcApplication19.Models
{
public class Employee
{
[ScaffoldColumn(false)]
public int Id { set; get; }
[Display(ResourceType = typeof(MvcApplication19.Views.Home.Index_cshtml),
Name = "Name")]
[Required(ErrorMessageResourceType = typeof(MvcApplication19.Views.Home.Index_cshtml),
ErrorMessageResourceName = "NameIsNotRight")]
public string Name { set; get; }
}
}
[OutputCache(Duration = 60, VaryByHeader = "Accept-Language")]
public ActionResult Index()