У меня есть веб-API ASP.NET 2 + OWIN CORS не работает — OPTIONS заблокированы, дублируются конвейеры и неправильное использование HttpConfiguration.
Я работаю над приложением ASP.NET Web API 2, размещенным в IIS с аутентификацией OWIN (OAuth + JWT).
Несмотря на несколько попыток, запросы CORS от интерфейса Angular завершаются с ошибками, связанными с предпроверкой (OPTIONS) запросы.
Даже после изменения
Startup.auth.cs
WebConfigAPI
Web.Config.
Global.asax.cs
namespace Pheonix.Web
{
public partial class Startup
{
//
// The Client ID is used by the application to uniquely identify itself to Azure AD.
// The Metadata Address is used by the application to retrieve the signing keys used by Azure AD.
// The AAD Instance is the instance of Azure, for example public Azure or Azure China.
// The Authority is the sign-in URL of the tenant.
// The Post Logout Redirect Uri is the URL where the user will be redirected after they sign out.
//
private static string clientId = ConfigurationManager.AppSettings["ida:ClientId"];
private static string aadInstance = ConfigurationManager.AppSettings["ida:AADInstance"];
private static string tenant = ConfigurationManager.AppSettings["ida:Tenant"];
private static string postLogoutRedirectUri = ConfigurationManager.AppSettings["ida:PostLogoutRedirectUri"];
static string authority = String.Format(CultureInfo.InvariantCulture, aadInstance, tenant);
public void ConfigureAuth(IAppBuilder app)
{
ConfigureDbContext(app);
ConfigureCookieAuthentication(app);
//ConfigureGoogleAuth(app);
ConfigureOffice365Auth(app);
ConfigureJwtOAuth(app);
}
private static void ConfigureDbContext(IAppBuilder app)
{
app.CreatePerOwinContext(ApplicationDbContext.Create);
app.CreatePerOwinContext(ApplicationUserManager.Create);
app.CreatePerOwinContext(ApplicationSignInManager.Create);
}
private static void ConfigureJwtOAuth(IAppBuilder app)
{
app.UseOAuthAuthorizationServer(new AppOAuthOptions());
app.UseJwtBearerAuthentication(new AppJwtOptions());
HttpConfiguration config = new HttpConfiguration();
app.UseWebApi(config);
}
private static void ConfigureCookieAuthentication(IAppBuilder app)
{
// Enable the application to use a cookie to store information for the signed in user
// and to use a cookie to temporarily store information about a user logging in with a third party login provider
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
LoginPath = new PathString("/Account/Login"),
CookieSecure = CookieSecureOption.Always,
Provider = new CookieAuthenticationProvider
{
// Enables the application to validate the security stamp when the user logs in.
// This is a security feature which is used when you change a password or add an external login to your account.
OnValidateIdentity = SecurityStampValidator.OnValidateIdentity(
validateInterval: TimeSpan.FromMinutes(500000),
regenerateIdentity: (manager, user) => user.GenerateUserIdentityAsync(manager, DefaultAuthenticationTypes.ApplicationCookie))
}
});
}
private static void ConfigureGoogleAuth(IAppBuilder app)
{
app.UseTwoFactorRememberBrowserCookie(DefaultAuthenticationTypes.TwoFactorRememberBrowserCookie);
app.UseGoogleAuthentication(new GoogleOAuth2AuthenticationOptions()
{
ClientId = "200899829118-64tcj5ofov7iofnkbojs83tjb9bpnlr0.apps.googleusercontent.com",
ClientSecret = "IOMqL-RsFOGdYFniESnNgIdL"
});
// Enables the application to temporarily store user information when they are verifying the second factor in the two-factor authentication process.
app.UseTwoFactorSignInCookie(DefaultAuthenticationTypes.TwoFactorCookie, TimeSpan.FromMinutes(5));
}
private static void ConfigureOffice365Auth(IAppBuilder app)
{
app.UseExternalSignInCookie(DefaultAuthenticationTypes.ExternalCookie);
app.UseTwoFactorRememberBrowserCookie(DefaultAuthenticationTypes.TwoFactorRememberBrowserCookie);
app.UseOpenIdConnectAuthentication(
new OpenIdConnectAuthenticationOptions
{
ClientId = clientId,
Authority = authority,
PostLogoutRedirectUri = postLogoutRedirectUri,
RedirectUri = postLogoutRedirectUri,
Notifications = new OpenIdConnectAuthenticationNotifications
{
AuthenticationFailed = context =>
{
context.HandleResponse();
context.Response.Redirect("/Error?message=" + context.Exception.Message);
return Task.FromResult(0);
}
}
});
}
}
public class AppOAuthOptions : OAuthAuthorizationServerOptions
{
public AppOAuthOptions()
{
TokenEndpointPath = new PathString("/token");
AccessTokenExpireTimeSpan = TimeSpan.FromMinutes(500000);
AccessTokenFormat = new AppJwtFormat(TimeSpan.FromMinutes(500000));
Provider = new AppOAuthProvider();
AllowInsecureHttp = true;
}
}
public class AppJwtFormat : ISecureDataFormat
{
private readonly TimeSpan _options;
public AppJwtFormat(TimeSpan options)
{
_options = options;
}
public string SignatureAlgorithm
{
get { return "http://www.w3.org/2001/04/xmldsig-more#hmac-sha256"; }
}
public string DigestAlgorithm
{
get { return "http://www.w3.org/2001/04/xmlenc#sha256"; }
}
public string Protect(AuthenticationTicket data)
{
if (data == null) throw new ArgumentNullException("data");
var issuer = "localhost";
var audience = "all";
var key = Convert.FromBase64String("bXlzdXBlcnN0cm9uZ2tleWZvckFwcFByb3RlY3Rpb24=");
var now = DateTime.UtcNow;
var expires = now.AddMinutes(_options.TotalMinutes);
var signingCredentials = new SigningCredentials(
new InMemorySymmetricSecurityKey(key),
SignatureAlgorithm,
DigestAlgorithm);
var token = new JwtSecurityToken(issuer, audience, data.Identity.Claims,
now, expires, signingCredentials);
return new JwtSecurityTokenHandler().WriteToken(token);
}
public AuthenticationTicket Unprotect(string protectedText)
{
throw new NotImplementedException();
}
}
public class AppOAuthProvider : OAuthAuthorizationServerProvider
{
public override Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context)
{
var identity = new ClaimsIdentity();
var username = context.OwinContext.Get("username");
var employee_id = context.OwinContext.Get("employee_id");
var role = context.OwinContext.Get("role");
var country = context.OwinContext.Get("country");
identity.AddClaim(new Claim(ClaimTypes.Email, username));
identity.AddClaim(new Claim(ClaimTypes.PrimarySid, employee_id));
identity.AddClaim(new Claim(ClaimTypes.Role, role));
identity.AddClaim(new Claim(ClaimTypes.Country, country));
context.Validated(identity);
return Task.FromResult(0);
}
public override Task ValidateClientAuthentication(OAuthValidateClientAuthenticationContext context)
{
try
{
var username = context.Parameters["username"];
var employee_id = context.Parameters["employee_id"];
PhoenixEntities dbContext = new PhoenixEntities();
var user = dbContext.PersonEmployment.Where(x => x.OrganizationEmail == username.ToLower() && x.OrganizationEmail.Length > 0).First();// && x.IsEnabled == true);
if (user != null)
{
context.OwinContext.Set("username", username);
context.OwinContext.Set("employee_id", user.PersonID.ToString());
context.OwinContext.Set("role", user.Person.PersonInRole.Any() ? string.Join(",", user.Person.PersonInRole.Select(t => t.RoleID).ToList()) : "20" );// RS: update role by adding EDMS
context.OwinContext.Set("country", user.OfficeLocation.Value.ToString());
context.Validated();
}
else
{
context.SetError("Invalid credentials");
context.Rejected();
}
}
catch
{
context.SetError("Server error");
context.Rejected();
}
return Task.FromResult(0);
}
}
public class AppJwtOptions : JwtBearerAuthenticationOptions
{
public AppJwtOptions()
{
var issuer = "localhost";
var audience = "all";
var key = Convert.FromBase64String("bXlzdXBlcnN0cm9uZ2tleWZvckFwcFByb3RlY3Rpb24="); ;
AllowedAudiences = new[] { audience };
IssuerSecurityTokenProviders = new[]
{
new SymmetricKeyIssuerSecurityTokenProvider(issuer, key)
};
}
}
}
namespace Pheonix.Web
{
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
// Web API configuration and services
//config.EnableCors();
ApiAccess.Initialize();
var json = GlobalConfiguration.Configuration.Formatters.JsonFormatter;
//json.SerializerSettings.DateFormatHandling = Newtonsoft.Json.DateFormatHandling.IsoDateFormat;
json.SerializerSettings.DateFormatString = "MM/dd/yyyy";// "mmm-dd-yyyy";
json.SerializerSettings.DateTimeZoneHandling = Newtonsoft.Json.DateTimeZoneHandling.Utc;
json.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver();
json.SupportedMediaTypes.Add(new MediaTypeHeaderValue("text/html"));
//json.SerializerSettings.Converters.Add(new IsoDateTimeConverter());
GlobalConfiguration.Configuration.MessageHandlers.Insert(0, new ServerCompressionHandler(new GZipCompressor(), new DeflateCompressor()));
config.MessageHandlers.Add(new BlockOptionsHandler());
var corsAttr = new EnableCorsAttribute(ConfigurationManager.AppSettings["WhitelistUrls"], "*", "*");
config.EnableCors(corsAttr);
// Configure Web API to use only bearer token authentication.
// Commented as it was not making the Controllers Authorize.
config.SuppressDefaultHostAuthentication();
config.Filters.Add(new HostAuthenticationFilter(OAuthDefaults.AuthenticationType));
config.Services.Add(typeof(IExceptionLogger), new PheonixExceptionLogger());
config.Services.Replace(typeof(IExceptionHandler), new PheonixExceptionHandler());
// Web API routes
config.MapHttpAttributeRoutes();
UnityConfig.RegisterComponents();
MappingDTOModelToModel.Configure();
// Or any other way to fetch your container.
//config.Routes.MapHttpRoute(
// name: "DefaultApi",
// routeTemplate: "api/{controller}/{id}",
// defaults: new { id = RouteParameter.Optional }
//);
}
}
}
[assembly: log4net.Config.XmlConfigurator(Watch = true)]
namespace Pheonix.Web
{
public class WebApiApplication : System.Web.HttpApplication
{
protected void Application_Start()
{
AreaRegistration.RegisterAllAreas();
GlobalConfiguration.Configure(WebApiConfig.Register);
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
RouteConfig.RegisterRoutes(RouteTable.Routes);
BundleConfig.RegisterBundles(BundleTable.Bundles);
HttpConfiguration config = GlobalConfiguration.Configuration;
MvcHandler.DisableMvcResponseHeader = true;
config.Formatters.JsonFormatter.SerializerSettings.ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore;
}
private readonly Stopwatch stopWatch = new Stopwatch();
private static readonly ILog Log4Net = LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);
protected void Application_BeginRequest(object sender, EventArgs e)
{
if (HttpContext.Current.Request.HttpMethod == "OPTIONS")
{
HttpContext.Current.Response.StatusCode = 405;
HttpContext.Current.Response.End();
}
ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12;
this.stopWatch.Start();
}
protected void Application_EndRequest(object sender, EventArgs e)
{
this.stopWatch.Stop();
Log4Net.ErrorFormat("Action : {0} : Time Utilized : {1} Seconds",
((System.Web.HttpApplication)(sender)).Context.Request.FilePath,
this.stopWatch.ElapsedMilliseconds / 1000);
this.stopWatch.Reset();
}
}
}
Подробнее здесь: https://stackoverflow.com/questions/798 ... pplication
Проблема CORS в приложении .NET 4.5 ⇐ C#
Место общения программистов C#
1767617639
Anonymous
У меня есть веб-API ASP.NET 2 + OWIN CORS не работает — OPTIONS заблокированы, дублируются конвейеры и неправильное использование HttpConfiguration.
Я работаю над приложением ASP.NET Web API 2, размещенным в IIS с аутентификацией OWIN (OAuth + JWT).
Несмотря на несколько попыток, запросы CORS от интерфейса Angular завершаются с ошибками, связанными с предпроверкой (OPTIONS) запросы.
Даже после изменения
Startup.auth.cs
WebConfigAPI
Web.Config.
Global.asax.cs
namespace Pheonix.Web
{
public partial class Startup
{
//
// The Client ID is used by the application to uniquely identify itself to Azure AD.
// The Metadata Address is used by the application to retrieve the signing keys used by Azure AD.
// The AAD Instance is the instance of Azure, for example public Azure or Azure China.
// The Authority is the sign-in URL of the tenant.
// The Post Logout Redirect Uri is the URL where the user will be redirected after they sign out.
//
private static string clientId = ConfigurationManager.AppSettings["ida:ClientId"];
private static string aadInstance = ConfigurationManager.AppSettings["ida:AADInstance"];
private static string tenant = ConfigurationManager.AppSettings["ida:Tenant"];
private static string postLogoutRedirectUri = ConfigurationManager.AppSettings["ida:PostLogoutRedirectUri"];
static string authority = String.Format(CultureInfo.InvariantCulture, aadInstance, tenant);
public void ConfigureAuth(IAppBuilder app)
{
ConfigureDbContext(app);
ConfigureCookieAuthentication(app);
//ConfigureGoogleAuth(app);
ConfigureOffice365Auth(app);
ConfigureJwtOAuth(app);
}
private static void ConfigureDbContext(IAppBuilder app)
{
app.CreatePerOwinContext(ApplicationDbContext.Create);
app.CreatePerOwinContext(ApplicationUserManager.Create);
app.CreatePerOwinContext(ApplicationSignInManager.Create);
}
private static void ConfigureJwtOAuth(IAppBuilder app)
{
app.UseOAuthAuthorizationServer(new AppOAuthOptions());
app.UseJwtBearerAuthentication(new AppJwtOptions());
HttpConfiguration config = new HttpConfiguration();
app.UseWebApi(config);
}
private static void ConfigureCookieAuthentication(IAppBuilder app)
{
// Enable the application to use a cookie to store information for the signed in user
// and to use a cookie to temporarily store information about a user logging in with a third party login provider
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
LoginPath = new PathString("/Account/Login"),
CookieSecure = CookieSecureOption.Always,
Provider = new CookieAuthenticationProvider
{
// Enables the application to validate the security stamp when the user logs in.
// This is a security feature which is used when you change a password or add an external login to your account.
OnValidateIdentity = SecurityStampValidator.OnValidateIdentity(
validateInterval: TimeSpan.FromMinutes(500000),
regenerateIdentity: (manager, user) => user.GenerateUserIdentityAsync(manager, DefaultAuthenticationTypes.ApplicationCookie))
}
});
}
private static void ConfigureGoogleAuth(IAppBuilder app)
{
app.UseTwoFactorRememberBrowserCookie(DefaultAuthenticationTypes.TwoFactorRememberBrowserCookie);
app.UseGoogleAuthentication(new GoogleOAuth2AuthenticationOptions()
{
ClientId = "200899829118-64tcj5ofov7iofnkbojs83tjb9bpnlr0.apps.googleusercontent.com",
ClientSecret = "IOMqL-RsFOGdYFniESnNgIdL"
});
// Enables the application to temporarily store user information when they are verifying the second factor in the two-factor authentication process.
app.UseTwoFactorSignInCookie(DefaultAuthenticationTypes.TwoFactorCookie, TimeSpan.FromMinutes(5));
}
private static void ConfigureOffice365Auth(IAppBuilder app)
{
app.UseExternalSignInCookie(DefaultAuthenticationTypes.ExternalCookie);
app.UseTwoFactorRememberBrowserCookie(DefaultAuthenticationTypes.TwoFactorRememberBrowserCookie);
app.UseOpenIdConnectAuthentication(
new OpenIdConnectAuthenticationOptions
{
ClientId = clientId,
Authority = authority,
PostLogoutRedirectUri = postLogoutRedirectUri,
RedirectUri = postLogoutRedirectUri,
Notifications = new OpenIdConnectAuthenticationNotifications
{
AuthenticationFailed = context =>
{
context.HandleResponse();
context.Response.Redirect("/Error?message=" + context.Exception.Message);
return Task.FromResult(0);
}
}
});
}
}
public class AppOAuthOptions : OAuthAuthorizationServerOptions
{
public AppOAuthOptions()
{
TokenEndpointPath = new PathString("/token");
AccessTokenExpireTimeSpan = TimeSpan.FromMinutes(500000);
AccessTokenFormat = new AppJwtFormat(TimeSpan.FromMinutes(500000));
Provider = new AppOAuthProvider();
AllowInsecureHttp = true;
}
}
public class AppJwtFormat : ISecureDataFormat
{
private readonly TimeSpan _options;
public AppJwtFormat(TimeSpan options)
{
_options = options;
}
public string SignatureAlgorithm
{
get { return "http://www.w3.org/2001/04/xmldsig-more#hmac-sha256"; }
}
public string DigestAlgorithm
{
get { return "http://www.w3.org/2001/04/xmlenc#sha256"; }
}
public string Protect(AuthenticationTicket data)
{
if (data == null) throw new ArgumentNullException("data");
var issuer = "localhost";
var audience = "all";
var key = Convert.FromBase64String("bXlzdXBlcnN0cm9uZ2tleWZvckFwcFByb3RlY3Rpb24=");
var now = DateTime.UtcNow;
var expires = now.AddMinutes(_options.TotalMinutes);
var signingCredentials = new SigningCredentials(
new InMemorySymmetricSecurityKey(key),
SignatureAlgorithm,
DigestAlgorithm);
var token = new JwtSecurityToken(issuer, audience, data.Identity.Claims,
now, expires, signingCredentials);
return new JwtSecurityTokenHandler().WriteToken(token);
}
public AuthenticationTicket Unprotect(string protectedText)
{
throw new NotImplementedException();
}
}
public class AppOAuthProvider : OAuthAuthorizationServerProvider
{
public override Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context)
{
var identity = new ClaimsIdentity();
var username = context.OwinContext.Get("username");
var employee_id = context.OwinContext.Get("employee_id");
var role = context.OwinContext.Get("role");
var country = context.OwinContext.Get("country");
identity.AddClaim(new Claim(ClaimTypes.Email, username));
identity.AddClaim(new Claim(ClaimTypes.PrimarySid, employee_id));
identity.AddClaim(new Claim(ClaimTypes.Role, role));
identity.AddClaim(new Claim(ClaimTypes.Country, country));
context.Validated(identity);
return Task.FromResult(0);
}
public override Task ValidateClientAuthentication(OAuthValidateClientAuthenticationContext context)
{
try
{
var username = context.Parameters["username"];
var employee_id = context.Parameters["employee_id"];
PhoenixEntities dbContext = new PhoenixEntities();
var user = dbContext.PersonEmployment.Where(x => x.OrganizationEmail == username.ToLower() && x.OrganizationEmail.Length > 0).First();// && x.IsEnabled == true);
if (user != null)
{
context.OwinContext.Set("username", username);
context.OwinContext.Set("employee_id", user.PersonID.ToString());
context.OwinContext.Set("role", user.Person.PersonInRole.Any() ? string.Join(",", user.Person.PersonInRole.Select(t => t.RoleID).ToList()) : "20" );// RS: update role by adding EDMS
context.OwinContext.Set("country", user.OfficeLocation.Value.ToString());
context.Validated();
}
else
{
context.SetError("Invalid credentials");
context.Rejected();
}
}
catch
{
context.SetError("Server error");
context.Rejected();
}
return Task.FromResult(0);
}
}
public class AppJwtOptions : JwtBearerAuthenticationOptions
{
public AppJwtOptions()
{
var issuer = "localhost";
var audience = "all";
var key = Convert.FromBase64String("bXlzdXBlcnN0cm9uZ2tleWZvckFwcFByb3RlY3Rpb24="); ;
AllowedAudiences = new[] { audience };
IssuerSecurityTokenProviders = new[]
{
new SymmetricKeyIssuerSecurityTokenProvider(issuer, key)
};
}
}
}
namespace Pheonix.Web
{
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
// Web API configuration and services
//config.EnableCors();
ApiAccess.Initialize();
var json = GlobalConfiguration.Configuration.Formatters.JsonFormatter;
//json.SerializerSettings.DateFormatHandling = Newtonsoft.Json.DateFormatHandling.IsoDateFormat;
json.SerializerSettings.DateFormatString = "MM/dd/yyyy";// "mmm-dd-yyyy";
json.SerializerSettings.DateTimeZoneHandling = Newtonsoft.Json.DateTimeZoneHandling.Utc;
json.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver();
json.SupportedMediaTypes.Add(new MediaTypeHeaderValue("text/html"));
//json.SerializerSettings.Converters.Add(new IsoDateTimeConverter());
GlobalConfiguration.Configuration.MessageHandlers.Insert(0, new ServerCompressionHandler(new GZipCompressor(), new DeflateCompressor()));
config.MessageHandlers.Add(new BlockOptionsHandler());
var corsAttr = new EnableCorsAttribute(ConfigurationManager.AppSettings["WhitelistUrls"], "*", "*");
config.EnableCors(corsAttr);
// Configure Web API to use only bearer token authentication.
// Commented as it was not making the Controllers Authorize.
config.SuppressDefaultHostAuthentication();
config.Filters.Add(new HostAuthenticationFilter(OAuthDefaults.AuthenticationType));
config.Services.Add(typeof(IExceptionLogger), new PheonixExceptionLogger());
config.Services.Replace(typeof(IExceptionHandler), new PheonixExceptionHandler());
// Web API routes
config.MapHttpAttributeRoutes();
UnityConfig.RegisterComponents();
MappingDTOModelToModel.Configure();
// Or any other way to fetch your container.
//config.Routes.MapHttpRoute(
// name: "DefaultApi",
// routeTemplate: "api/{controller}/{id}",
// defaults: new { id = RouteParameter.Optional }
//);
}
}
}
[assembly: log4net.Config.XmlConfigurator(Watch = true)]
namespace Pheonix.Web
{
public class WebApiApplication : System.Web.HttpApplication
{
protected void Application_Start()
{
AreaRegistration.RegisterAllAreas();
GlobalConfiguration.Configure(WebApiConfig.Register);
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
RouteConfig.RegisterRoutes(RouteTable.Routes);
BundleConfig.RegisterBundles(BundleTable.Bundles);
HttpConfiguration config = GlobalConfiguration.Configuration;
MvcHandler.DisableMvcResponseHeader = true;
config.Formatters.JsonFormatter.SerializerSettings.ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore;
}
private readonly Stopwatch stopWatch = new Stopwatch();
private static readonly ILog Log4Net = LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);
protected void Application_BeginRequest(object sender, EventArgs e)
{
if (HttpContext.Current.Request.HttpMethod == "OPTIONS")
{
HttpContext.Current.Response.StatusCode = 405;
HttpContext.Current.Response.End();
}
ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12;
this.stopWatch.Start();
}
protected void Application_EndRequest(object sender, EventArgs e)
{
this.stopWatch.Stop();
Log4Net.ErrorFormat("Action : {0} : Time Utilized : {1} Seconds",
((System.Web.HttpApplication)(sender)).Context.Request.FilePath,
this.stopWatch.ElapsedMilliseconds / 1000);
this.stopWatch.Reset();
}
}
}
Подробнее здесь: [url]https://stackoverflow.com/questions/79860775/cors-issue-in-net-4-5-application[/url]
Ответить
1 сообщение
• Страница 1 из 1
Перейти
- Кемерово-IT
- ↳ Javascript
- ↳ C#
- ↳ JAVA
- ↳ Elasticsearch aggregation
- ↳ Python
- ↳ Php
- ↳ Android
- ↳ Html
- ↳ Jquery
- ↳ C++
- ↳ IOS
- ↳ CSS
- ↳ Excel
- ↳ Linux
- ↳ Apache
- ↳ MySql
- Детский мир
- Для души
- ↳ Музыкальные инструменты даром
- ↳ Печатная продукция даром
- Внешняя красота и здоровье
- ↳ Одежда и обувь для взрослых даром
- ↳ Товары для здоровья
- ↳ Физкультура и спорт
- Техника - даром!
- ↳ Автомобилистам
- ↳ Компьютерная техника
- ↳ Плиты: газовые и электрические
- ↳ Холодильники
- ↳ Стиральные машины
- ↳ Телевизоры
- ↳ Телефоны, смартфоны, плашеты
- ↳ Швейные машинки
- ↳ Прочая электроника и техника
- ↳ Фототехника
- Ремонт и интерьер
- ↳ Стройматериалы, инструмент
- ↳ Мебель и предметы интерьера даром
- ↳ Cантехника
- Другие темы
- ↳ Разное даром
- ↳ Давай меняться!
- ↳ Отдам\возьму за копеечку
- ↳ Работа и подработка в Кемерове
- ↳ Давай с тобой поговорим...
Мобильная версия