From 0528eb69711474413ad5ac312532c72c1d387734 Mon Sep 17 00:00:00 2001 From: sFedyashov Date: Mon, 19 Oct 2015 01:13:42 +0300 Subject: [PATCH 1/4] =?UTF-8?q?=D0=A1=D0=BE=D0=B7=D0=B4=D0=B0=D0=BD=D0=B8?= =?UTF-8?q?=D0=B5=20=D1=81=D0=BB=D0=BE=D1=8F=20=D0=B4=D0=BE=D1=81=D1=82?= =?UTF-8?q?=D1=83=D0=BF=D0=B0=20=D0=BA=20=D0=B4=D0=B0=D0=BD=D0=BD=D1=8B?= =?UTF-8?q?=D0=BC.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Создан слой работы с данными. В самом приложении/тестах работа только через него. Операции, специфичные для конкретной СУБД (MongoDB) убраны в отдельный датапровайдер. Тем самым убираем связность (Issue #29) и готовим приложение для работы с другими БД (Issue #28) --- .gitignore | 4 + .../BuildRevisionCounter.Tests.csproj | 7 +- .../BuildRevisionCounterTest.cs | 2 +- .../Controllers/CounterControllerTest.cs | 6 +- .../DBStorageFactory.cs | 23 +++ ...MongoDBStorageTest.cs => DBStorageTest.cs} | 37 ++--- .../IntegrationTest/IntegrationTest.cs | 2 +- .../MongoDBStorageFactory.cs | 27 --- .../MongoDBStorageUtils.cs | 17 -- .../BuildRevisionCounter.csproj | 12 +- .../Controllers/CounterController.cs | 82 ++------- BuildRevisionCounter/Data/DbStorage.cs | 110 +++++++++++++ .../Data/DuplicateKeyException.cs | 9 + BuildRevisionCounter/Data/MongoDBStorage.cs | 155 ++++++++++++++++++ .../Interfaces/IDataProvider.cs | 36 ++++ .../Interfaces/IMongoDBStorage.cs | 21 --- BuildRevisionCounter/Model/RevisionModel.cs | 3 - BuildRevisionCounter/MongoDBStorage.cs | 63 ------- .../Security/BasicAuthenticationFilter.cs | 21 +-- BuildRevisionCounter/Startup.cs | 16 +- .../PersistentCaches/MANIFEST-000013 | Bin 0 -> 978 bytes 21 files changed, 393 insertions(+), 260 deletions(-) create mode 100644 BuildRevisionCounter.Tests/DBStorageFactory.cs rename BuildRevisionCounter.Tests/{MongoDBStorageTest.cs => DBStorageTest.cs} (58%) delete mode 100644 BuildRevisionCounter.Tests/MongoDBStorageFactory.cs delete mode 100644 BuildRevisionCounter.Tests/MongoDBStorageUtils.cs create mode 100644 BuildRevisionCounter/Data/DbStorage.cs create mode 100644 BuildRevisionCounter/Data/DuplicateKeyException.cs create mode 100644 BuildRevisionCounter/Data/MongoDBStorage.cs create mode 100644 BuildRevisionCounter/Interfaces/IDataProvider.cs delete mode 100644 BuildRevisionCounter/Interfaces/IMongoDBStorage.cs delete mode 100644 BuildRevisionCounter/MongoDBStorage.cs create mode 100644 _ReSharper.BuildRevisionCounter/PersistentCaches/MANIFEST-000013 diff --git a/.gitignore b/.gitignore index a3da8eb..0a29cad 100644 --- a/.gitignore +++ b/.gitignore @@ -11,3 +11,7 @@ TestResults TestResult.xml /packages/ /publish/ +*.bin +*.dat +*.sst +*.version diff --git a/BuildRevisionCounter.Tests/BuildRevisionCounter.Tests.csproj b/BuildRevisionCounter.Tests/BuildRevisionCounter.Tests.csproj index 8ce0b45..ffa366b 100644 --- a/BuildRevisionCounter.Tests/BuildRevisionCounter.Tests.csproj +++ b/BuildRevisionCounter.Tests/BuildRevisionCounter.Tests.csproj @@ -17,6 +17,8 @@ False UnitTest + ..\ + true true @@ -92,10 +94,9 @@ - - + + - diff --git a/BuildRevisionCounter.Tests/BuildRevisionCounterTest.cs b/BuildRevisionCounter.Tests/BuildRevisionCounterTest.cs index 6ebc80f..09bc438 100644 --- a/BuildRevisionCounter.Tests/BuildRevisionCounterTest.cs +++ b/BuildRevisionCounter.Tests/BuildRevisionCounterTest.cs @@ -11,7 +11,7 @@ public class BuildRevisionCounterTest : IntegrationTest [Test] public async Task GetAllRevisions() { - var apiUri = "api/counter"; + const string apiUri = "api/counter"; var body = await SendGetRequest(apiUri); dynamic result = JArray.Parse(body); diff --git a/BuildRevisionCounter.Tests/Controllers/CounterControllerTest.cs b/BuildRevisionCounter.Tests/Controllers/CounterControllerTest.cs index c4bfe67..756e1b7 100644 --- a/BuildRevisionCounter.Tests/Controllers/CounterControllerTest.cs +++ b/BuildRevisionCounter.Tests/Controllers/CounterControllerTest.cs @@ -14,9 +14,9 @@ public class CounterControllerTest [TestFixtureSetUp] public void SetUp() { - MongoDBStorageUtils.SetUpAsync().Wait(); - - _controller = new CounterController(MongoDBStorageFactory.DefaultInstance); + DBStorageFactory.DefaultInstance.SetUpAsync().Wait(); + + _controller = new CounterController(DBStorageFactory.DefaultInstance); } [Test] diff --git a/BuildRevisionCounter.Tests/DBStorageFactory.cs b/BuildRevisionCounter.Tests/DBStorageFactory.cs new file mode 100644 index 0000000..6e9505d --- /dev/null +++ b/BuildRevisionCounter.Tests/DBStorageFactory.cs @@ -0,0 +1,23 @@ +using System; +using BuildRevisionCounter.Data; + +namespace BuildRevisionCounter.Tests +{ + public static class DBStorageFactory + { + private static readonly Lazy _defaultInstance = + new Lazy(() => FromConfigurationConnectionString()); + + public static DbStorage DefaultInstance { get { return _defaultInstance.Value; } } + + public static DbStorage FromConnectionString(string connectionStringName) + { + return new DbStorage(connectionStringName); + } + + public static DbStorage FromConfigurationConnectionString(string connectionStringName = "MongoDBStorage") + { + return FromConnectionString(connectionStringName); + } + } +} \ No newline at end of file diff --git a/BuildRevisionCounter.Tests/MongoDBStorageTest.cs b/BuildRevisionCounter.Tests/DBStorageTest.cs similarity index 58% rename from BuildRevisionCounter.Tests/MongoDBStorageTest.cs rename to BuildRevisionCounter.Tests/DBStorageTest.cs index c498db1..5a489a8 100644 --- a/BuildRevisionCounter.Tests/MongoDBStorageTest.cs +++ b/BuildRevisionCounter.Tests/DBStorageTest.cs @@ -1,13 +1,13 @@ using System.Threading.Tasks; -using MongoDB.Driver; +using BuildRevisionCounter.Data; using NUnit.Framework; namespace BuildRevisionCounter.Tests { [TestFixture] - public class MongoDBStorageTest + public class DBStorageTest { - private MongoDBStorage _storage; + private DbStorage _storage; [TestFixtureSetUp] public void SetUp() @@ -17,19 +17,17 @@ public void SetUp() public async Task SetUpAsync() { - _storage = MongoDBStorageFactory.DefaultInstance; - - await _storage.Revisions.Database.Client.DropDatabaseAsync( - _storage.Revisions.Database.DatabaseNamespace.DatabaseName); - - await _storage.SetUp(); + _storage = DBStorageFactory.DefaultInstance; + await _storage.SetUpAsync(); } [Test] public async Task EnsureAdminUserCreated() { - var user = await _storage.FindUser(MongoDBStorage.AdminName); - Assert.AreEqual(MongoDBStorage.AdminName, user.Name); + var adminName = _storage.GetAdminName(); + var user = await _storage.FindUser(adminName); + Assert.IsNotNull(user); + Assert.AreEqual(adminName, user.Name); } [Test] @@ -63,22 +61,17 @@ public async Task EnsureAdminUserMayBeInvokedMultipleTimes() [Test] public async Task CreateUserMustThrowExceptionIfUserExists() { - await _storage.CreateUser( - "CreateUserMustThrowExceptionIfUserExists", - "CreateUserMustThrowExceptionIfUserExists", - new[] {"testRole"}); - + const string userName = "CreateUserMustThrowExceptionIfUserExists"; + const string userPass = "CreateUserMustThrowExceptionIfUserExists"; + var userRole = new[] {"testRole"}; + await _storage.CreateUser(userName, userPass, userRole); try { - await _storage.CreateUser( - "CreateUserMustThrowExceptionIfUserExists", - "CreateUserMustThrowExceptionIfUserExists", - new[] {"testRole"}); + await _storage.CreateUser(userName, userPass, userRole); Assert.Fail(); } - catch (MongoWriteException ex) + catch (DuplicateKeyException) { - Assert.AreEqual(ServerErrorCategory.DuplicateKey, ex.WriteError.Category); } } } diff --git a/BuildRevisionCounter.Tests/IntegrationTest/IntegrationTest.cs b/BuildRevisionCounter.Tests/IntegrationTest/IntegrationTest.cs index 08658a6..1dfcaf6 100644 --- a/BuildRevisionCounter.Tests/IntegrationTest/IntegrationTest.cs +++ b/BuildRevisionCounter.Tests/IntegrationTest/IntegrationTest.cs @@ -27,7 +27,7 @@ public void Setup() _uri = string.Format("http://localhost:{0}", port); _application = WebApp.Start(_uri); - MongoDBStorageUtils.SetUpAsync().Wait(); + DBStorageFactory.DefaultInstance.SetUpAsync().Wait(); } diff --git a/BuildRevisionCounter.Tests/MongoDBStorageFactory.cs b/BuildRevisionCounter.Tests/MongoDBStorageFactory.cs deleted file mode 100644 index 9a072dd..0000000 --- a/BuildRevisionCounter.Tests/MongoDBStorageFactory.cs +++ /dev/null @@ -1,27 +0,0 @@ -using System; -using System.Configuration; -using MongoDB.Driver; - -namespace BuildRevisionCounter.Tests -{ - public static class MongoDBStorageFactory - { - private static readonly Lazy _defaultInstance = - new Lazy(() => FromConfigurationConnectionString()); - - public static MongoDBStorage DefaultInstance { get { return _defaultInstance.Value; } } - - public static MongoDBStorage FromConnectionString(string connectionString) - { - var mongoUrl = MongoUrl.Create(connectionString); - var database = new MongoClient(mongoUrl).GetDatabase(mongoUrl.DatabaseName); - return new MongoDBStorage(database); - } - - public static MongoDBStorage FromConfigurationConnectionString(string connectionStringName = "MongoDBStorage") - { - var connectionString = ConfigurationManager.ConnectionStrings[connectionStringName].ConnectionString; - return FromConnectionString(connectionString); - } - } -} \ No newline at end of file diff --git a/BuildRevisionCounter.Tests/MongoDBStorageUtils.cs b/BuildRevisionCounter.Tests/MongoDBStorageUtils.cs deleted file mode 100644 index 11759ee..0000000 --- a/BuildRevisionCounter.Tests/MongoDBStorageUtils.cs +++ /dev/null @@ -1,17 +0,0 @@ -using System; -using System.Threading.Tasks; - -namespace BuildRevisionCounter.Tests -{ - internal class MongoDBStorageUtils - { - public static async Task SetUpAsync() - { - var storage = MongoDBStorageFactory.DefaultInstance; - await storage.Revisions.Database.Client.DropDatabaseAsync( - storage.Revisions.Database.DatabaseNamespace.DatabaseName); - - await storage.SetUp(); - } - } -} diff --git a/BuildRevisionCounter/BuildRevisionCounter.csproj b/BuildRevisionCounter/BuildRevisionCounter.csproj index 596f9ab..c86bafd 100644 --- a/BuildRevisionCounter/BuildRevisionCounter.csproj +++ b/BuildRevisionCounter/BuildRevisionCounter.csproj @@ -20,6 +20,8 @@ + ..\ + true true @@ -113,17 +115,21 @@ - + + Designer + - + + + - + diff --git a/BuildRevisionCounter/Controllers/CounterController.cs b/BuildRevisionCounter/Controllers/CounterController.cs index ae9e355..cfbee0f 100644 --- a/BuildRevisionCounter/Controllers/CounterController.cs +++ b/BuildRevisionCounter/Controllers/CounterController.cs @@ -3,10 +3,9 @@ using System.Net; using System.Threading.Tasks; using System.Web.Http; -using BuildRevisionCounter.Interfaces; using BuildRevisionCounter.Model; using BuildRevisionCounter.Security; -using MongoDB.Driver; +using BuildRevisionCounter.Data; namespace BuildRevisionCounter.Controllers { @@ -14,18 +13,18 @@ namespace BuildRevisionCounter.Controllers [BasicAuthentication] public class CounterController : ApiController { - private readonly IMongoDBStorage _mongoDbStorage; + private readonly DbStorage _dbStorage; /// /// Конструктор контроллера номеров ревизий. /// - /// Объект для получения данных из БД Монго. - public CounterController(IMongoDBStorage mongoDbStorage) + /// Объект для получения данных из БД. + public CounterController(DbStorage dbStorage) { - if (mongoDbStorage == null) - throw new ArgumentNullException("mongoDbStorage"); + if (dbStorage == null) + throw new ArgumentNullException("dbStorage"); - _mongoDbStorage = mongoDbStorage; + _dbStorage = dbStorage; } [HttpGet] @@ -36,11 +35,7 @@ public async Task> GetAllRevision([FromUri] I if (pageSize < 1 || pageNumber < 1) throw new HttpResponseException(HttpStatusCode.BadRequest); - var revisions = await _mongoDbStorage.Revisions - .Find(r => true) - .Skip(pageSize * (pageNumber - 1)) - .Limit(pageSize) - .ToListAsync(); + var revisions = await _dbStorage.GetAllRevision(pageSize, pageNumber); if (revisions == null) throw new HttpResponseException(HttpStatusCode.NotFound); @@ -53,14 +48,12 @@ public async Task> GetAllRevision([FromUri] I [Authorize(Roles = "admin, editor, anonymous")] public async Task Current([FromUri] string revisionName) { - var revision = await _mongoDbStorage.Revisions - .Find(r => r.Id == revisionName) - .SingleOrDefaultAsync(); + var revisionNumber = await _dbStorage.CurrentRevision(revisionName); - if (revision == null) + if (revisionNumber == null) throw new HttpResponseException(HttpStatusCode.NotFound); - return revision.CurrentNumber; + return revisionNumber.Value; } [HttpPost] @@ -68,58 +61,7 @@ public async Task Current([FromUri] string revisionName) [Authorize(Roles = "buildserver")] public async Task Bumping([FromUri] string revisionName) { - // попробуем обновить документ - var result = await FindOneAndUpdateRevisionModelAsync(revisionName); - if (result != null) - return result.CurrentNumber; - - // если не получилось, значит документ еще не был создан - // создадим его с начальным значением 0 - try - { - await _mongoDbStorage.Revisions - .InsertOneAsync(new RevisionModel - { - Id = revisionName, - CurrentNumber = 0, - Created = DateTime.UtcNow - }); - return 0; - } - catch (MongoWriteException ex) - { - if (ex.WriteError.Category != ServerErrorCategory.DuplicateKey) - throw; - } - - // если при вставке произошла ошибка значит мы не успели и запись там уже есть - // и теперь попытка обновления должна пройти без ошибок - result = await FindOneAndUpdateRevisionModelAsync(revisionName); - - return result.CurrentNumber; - } - - /// - /// Инкриментит каунтер в БД - /// - /// - /// - private async Task FindOneAndUpdateRevisionModelAsync(string revisionName) - { - var result = await _mongoDbStorage.Revisions - .FindOneAndUpdateAsync( - r => r.Id == revisionName, - Builders.Update - .Inc(r => r.CurrentNumber, 1) - .SetOnInsert(r => r.Created, DateTime.UtcNow) - .Set(r => r.Updated, DateTime.UtcNow), - new FindOneAndUpdateOptions - { - IsUpsert = false, - ReturnDocument = ReturnDocument.After - }); - - return result; + return await _dbStorage.Bumping(revisionName); } } } \ No newline at end of file diff --git a/BuildRevisionCounter/Data/DbStorage.cs b/BuildRevisionCounter/Data/DbStorage.cs new file mode 100644 index 0000000..655b88c --- /dev/null +++ b/BuildRevisionCounter/Data/DbStorage.cs @@ -0,0 +1,110 @@ +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using System.Configuration; +using BuildRevisionCounter.Interfaces; +using BuildRevisionCounter.Model; + +namespace BuildRevisionCounter.Data +{ + public class DbStorage + { + private readonly IDataProvider _dataProvider; + + public DbStorage(string connectionStringName = "MongoDBStorage") + { + _dataProvider = GetDataProvider(connectionStringName); + } + + #region функционал сервиса + + private static IDataProvider GetDataProvider(string connectionStringName) + { + const string typeName = "BuildRevisionCounter.Data.MongoDBStorage"; + var type = Type.GetType(typeName); + if (type == null) + throw new ApplicationException("на найден класс для IDataProvider"); + var connectionString = ConfigurationManager.ConnectionStrings[connectionStringName].ConnectionString; + return (IDataProvider)Activator.CreateInstance(type, connectionString); + } + + public async Task> GetAllRevision(Int32 pageSize, Int32 pageNumber) + { + return await _dataProvider.GetAllRevision(pageSize, pageNumber); + } + + public async Task CurrentRevision(string revisionName) + { + return await _dataProvider.CurrentRevision(revisionName); + } + + public async Task Bumping(string revisionName) + { + // попробуем обновить документ + var result = await FindOneAndUpdateRevisionModelAsync(revisionName); + if (result != null) + return result.CurrentNumber; + + // если не получилось, значит документ еще не был создан + // создадим его с начальным значением 0 + try + { + await _dataProvider.RevisionInsertAsync(revisionName); + return 0; + } + catch (DuplicateKeyException) + { + } + + // если при вставке произошла ошибка значит мы не успели и запись там уже есть + // и теперь попытка обновления должна пройти без ошибок + result = await FindOneAndUpdateRevisionModelAsync(revisionName); + + return result.CurrentNumber; + } + + /// + /// Увеличивает счетчик в БД + /// + /// + /// + private async Task FindOneAndUpdateRevisionModelAsync(string revisionName) + { + return await _dataProvider.FindOneAndUpdateRevisionModelAsync(revisionName); + } + + #endregion + + public async Task SetUpAsync() + { + await _dataProvider.DropDatabaseAsync(); + await _dataProvider.SetUp(); + } + + #region операции с пользователями + + public string GetAdminName() + { + return _dataProvider.AdminName; + } + + public async Task FindUser(string name) + { + return await _dataProvider.FindUser(name); + } + + public async Task CreateUser(string name, string password, string[] roles) + { + await _dataProvider.CreateUser(name, password, roles); + } + + public async Task EnsureAdminUser() + { + await _dataProvider.EnsureAdminUser(); + } + + + + #endregion + } +} \ No newline at end of file diff --git a/BuildRevisionCounter/Data/DuplicateKeyException.cs b/BuildRevisionCounter/Data/DuplicateKeyException.cs new file mode 100644 index 0000000..370e3bb --- /dev/null +++ b/BuildRevisionCounter/Data/DuplicateKeyException.cs @@ -0,0 +1,9 @@ +using System; + +namespace BuildRevisionCounter.Data +{ + [Serializable] + public class DuplicateKeyException : InvalidOperationException + { + } +} \ No newline at end of file diff --git a/BuildRevisionCounter/Data/MongoDBStorage.cs b/BuildRevisionCounter/Data/MongoDBStorage.cs new file mode 100644 index 0000000..3d5a993 --- /dev/null +++ b/BuildRevisionCounter/Data/MongoDBStorage.cs @@ -0,0 +1,155 @@ +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using BuildRevisionCounter.Interfaces; +using BuildRevisionCounter.Model; +using MongoDB.Driver; + +namespace BuildRevisionCounter.Data +{ + public class MongoDBStorage : IDataProvider + { + public string AdminName + { + get { return "admin"; } + } + + public static readonly string AdminPassword = "admin"; + public static readonly string[] AdminRoles = {"admin", "buildserver", "editor"}; + + private readonly IMongoCollection _revisions; + private readonly IMongoCollection _users; + + public MongoDBStorage(string connectionString) + { + var mongoUrl = MongoUrl.Create(connectionString); + var database = new MongoClient(mongoUrl).GetDatabase(mongoUrl.DatabaseName); + + _revisions = database.GetCollection("revisions"); + _users = database.GetCollection("users"); + + Task.Run(() => SetUp()).Wait(); + } + + public async Task SetUp() + { + await EnsureUsersIndex(); + await EnsureAdminUser(); + } + + public Task DropDatabaseAsync() + { + return _revisions.Database.Client.DropDatabaseAsync(_revisions.Database.DatabaseNamespace.DatabaseName); + } + + public async Task> GetAllRevision(Int32 pageSize, Int32 pageNumber) + { + var revisions = await _revisions + .Find(r => true) + .Skip(pageSize * (pageNumber - 1)) + .Limit(pageSize) + .ToListAsync(); + + return revisions; + } + + public async Task CurrentRevision(string revisionName) + { + var revision = await _revisions + .Find(r => r.Id == revisionName) + .SingleOrDefaultAsync(); + + if (revision == null) + return null; + + return revision.CurrentNumber; + } + + public async Task RevisionInsertAsync(string revisionName) + { + try + { + await _revisions + .InsertOneAsync(new RevisionModel + { + Id = revisionName, + CurrentNumber = 0, + Created = DateTime.UtcNow + }); + } + catch (MongoWriteException ex) + { + if (ex.WriteError != null && ex.WriteError.Category == ServerErrorCategory.DuplicateKey) + throw new DuplicateKeyException(); + throw; + } + } + + public async Task FindOneAndUpdateRevisionModelAsync(string revisionName) + { + var result = await _revisions + .FindOneAndUpdateAsync( + r => r.Id == revisionName, + Builders.Update + .Inc(r => r.CurrentNumber, 1) + .SetOnInsert(r => r.Created, DateTime.UtcNow) + .Set(r => r.Updated, DateTime.UtcNow), + new FindOneAndUpdateOptions + { + IsUpsert = false, + ReturnDocument = ReturnDocument.After + }); + + return result; + } + + public async Task EnsureUsersIndex() + { + await _users.Indexes.CreateOneAsync( + Builders.IndexKeys.Ascending(u => u.Name), + new CreateIndexOptions { Unique = true, }); + } + + public async Task EnsureAdminUser() + { + if (await _users.CountAsync(_ => true) == 0) + { + await CreateUser(AdminName, AdminPassword, AdminRoles); + } + } + + public async Task FindUser(string name) + { + return await _users.Find(u => u.Name == name).SingleOrDefaultAsync(); + } + + /// + /// создать пользователя + /// + /// имя + /// пароль + /// роли + /// + /// В случае дублирования имени пользователя + public async Task CreateUser(string name, string password, string[] roles) + { + try + { + await _users + .InsertOneAsync( + new UserModel + { + Name = name, + Password = password, + Roles = roles + }); + } + catch (MongoWriteException ex) + { + if (ex.WriteError != null && ex.WriteError.Category == ServerErrorCategory.DuplicateKey) + throw new DuplicateKeyException(); + throw; + } + } + } +} \ No newline at end of file diff --git a/BuildRevisionCounter/Interfaces/IDataProvider.cs b/BuildRevisionCounter/Interfaces/IDataProvider.cs new file mode 100644 index 0000000..8958281 --- /dev/null +++ b/BuildRevisionCounter/Interfaces/IDataProvider.cs @@ -0,0 +1,36 @@ +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using BuildRevisionCounter.Model; +using MongoDB.Driver; + +namespace BuildRevisionCounter.Interfaces +{ + /// + /// Интерфейс для получения данных из БД. + /// + public interface IDataProvider + { + string AdminName { get; } + + Task> GetAllRevision(Int32 pageSize, Int32 pageNumber); + + Task CurrentRevision(string revisionName); + + Task RevisionInsertAsync(string revisionName); + + Task FindOneAndUpdateRevisionModelAsync(string revisionName); + + Task SetUp(); + + Task DropDatabaseAsync(); + + Task FindUser(string name); + + + + Task CreateUser(string name, string password, string[] roles); + + Task EnsureAdminUser(); + } +} \ No newline at end of file diff --git a/BuildRevisionCounter/Interfaces/IMongoDBStorage.cs b/BuildRevisionCounter/Interfaces/IMongoDBStorage.cs deleted file mode 100644 index c75dc23..0000000 --- a/BuildRevisionCounter/Interfaces/IMongoDBStorage.cs +++ /dev/null @@ -1,21 +0,0 @@ -using BuildRevisionCounter.Model; -using MongoDB.Driver; - -namespace BuildRevisionCounter.Interfaces -{ - /// - /// Интерфейс для получения данных из БД Монго. - /// - public interface IMongoDBStorage - { - /// - /// MongoDB-коллекция ревизий. - /// - IMongoCollection Revisions { get; } - - /// - /// MongoDB-коллекция пользователей. - /// - IMongoCollection Users { get; } - } -} \ No newline at end of file diff --git a/BuildRevisionCounter/Model/RevisionModel.cs b/BuildRevisionCounter/Model/RevisionModel.cs index 96adbbd..a3175ca 100644 --- a/BuildRevisionCounter/Model/RevisionModel.cs +++ b/BuildRevisionCounter/Model/RevisionModel.cs @@ -1,8 +1,5 @@ using MongoDB.Bson.Serialization.Attributes; using System; -using System.Collections.Generic; -using System.Linq; -using System.Web; namespace BuildRevisionCounter.Model { diff --git a/BuildRevisionCounter/MongoDBStorage.cs b/BuildRevisionCounter/MongoDBStorage.cs deleted file mode 100644 index f5d9c84..0000000 --- a/BuildRevisionCounter/MongoDBStorage.cs +++ /dev/null @@ -1,63 +0,0 @@ -using System.Threading.Tasks; -using BuildRevisionCounter.Interfaces; -using BuildRevisionCounter.Model; -using MongoDB.Driver; - -namespace BuildRevisionCounter -{ - public class MongoDBStorage : IMongoDBStorage - { - public static readonly string AdminName = "admin"; - public static readonly string AdminPassword = "admin"; - public static readonly string[] AdminRoles = {"admin", "buildserver", "editor"}; - - public IMongoCollection Revisions { get; private set; } - public IMongoCollection Users { get; private set; } - - public MongoDBStorage(IMongoDatabase database) - { - Revisions = database.GetCollection("revisions"); - Users = database.GetCollection("users"); - - Task.Run(() => SetUp()).Wait(); - } - - public async Task SetUp() - { - await EnsureUsersIndex(); - await EnsureAdminUser(); - } - - public async Task EnsureUsersIndex() - { - await Users.Indexes.CreateOneAsync( - Builders.IndexKeys.Ascending(u => u.Name), - new CreateIndexOptions { Unique = true, }); - } - - public async Task EnsureAdminUser() - { - if (await Users.CountAsync(_ => true) == 0) - { - await CreateUser(AdminName, AdminPassword, AdminRoles); - } - } - - public async Task FindUser(string name) - { - return await Users.Find(u => u.Name == name).SingleOrDefaultAsync(); - } - - public Task CreateUser(string name, string password, string[] roles) - { - return Users - .InsertOneAsync( - new UserModel - { - Name = name, - Password = password, - Roles = roles - }); - } - } -} \ No newline at end of file diff --git a/BuildRevisionCounter/Security/BasicAuthenticationFilter.cs b/BuildRevisionCounter/Security/BasicAuthenticationFilter.cs index e8a5228..0c5a49f 100644 --- a/BuildRevisionCounter/Security/BasicAuthenticationFilter.cs +++ b/BuildRevisionCounter/Security/BasicAuthenticationFilter.cs @@ -6,9 +6,7 @@ using System.Threading; using System.Threading.Tasks; using System.Web.Http.Filters; -using BuildRevisionCounter.Interfaces; -using BuildRevisionCounter.Model; -using MongoDB.Driver; +using BuildRevisionCounter.Data; namespace BuildRevisionCounter.Security { @@ -29,18 +27,18 @@ public class BasicAuthenticationFilter : IAuthenticationFilter EncoderFallback.ExceptionFallback, DecoderFallback.ExceptionFallback); - private readonly IMongoDBStorage _mongoDbStorage; + private readonly DbStorage _dbStorage; /// /// Конструктор фильтра. /// - /// Объект для получения данных из БД Монго. - public BasicAuthenticationFilter(IMongoDBStorage mongoDbStorage) + /// Объект для получения данных из БД. + public BasicAuthenticationFilter(DbStorage dbStorage) { - if (mongoDbStorage == null) - throw new ArgumentNullException("mongoDbStorage"); + if (dbStorage == null) + throw new ArgumentNullException("dbStorage"); - _mongoDbStorage = mongoDbStorage; + _dbStorage = dbStorage; } public string Realm { get; set; } @@ -89,10 +87,7 @@ public Task ChallengeAsync(HttpAuthenticationChallengeContext context, Cancellat private async Task Authenticate(string userName, string password) { IPrincipal principal = null; - var user = - await - _mongoDbStorage.Users.Find(Builders.Filter.Where(u => u.Name == userName)) - .SingleOrDefaultAsync(); + var user = await _dbStorage.FindUser(userName); if (user != null && user.Password == password) { principal = new GenericPrincipal(new GenericIdentity(userName), user.Roles); diff --git a/BuildRevisionCounter/Startup.cs b/BuildRevisionCounter/Startup.cs index fc77cfa..5339283 100644 --- a/BuildRevisionCounter/Startup.cs +++ b/BuildRevisionCounter/Startup.cs @@ -1,8 +1,7 @@ -using System.Configuration; -using System.Web.Http.Filters; +using System.Web.Http.Filters; +using BuildRevisionCounter.Data; using BuildRevisionCounter.Security; using Microsoft.Owin; -using MongoDB.Driver; using Ninject; using Ninject.Web.Common.OwinHost; using Ninject.Web.WebApi.FilterBindingSyntax; @@ -11,7 +10,6 @@ using BuildRevisionCounter; using System.Web.Http; using System.Net.Http.Formatting; -using BuildRevisionCounter.Interfaces; [assembly: OwinStartup(typeof(Startup))] @@ -58,16 +56,8 @@ private static IKernel CreateKernel() /// Ядро Ninject. private static void RegisterServices(IKernel kernel) { - kernel.Bind().ToMethod(c => GetMongoDbStorage()).InSingletonScope(); + kernel.Bind().ToMethod(c => new DbStorage()).InSingletonScope(); kernel.BindHttpFilter(FilterScope.Controller).WhenControllerHas(); } - - private static MongoDBStorage GetMongoDbStorage(string connectionStringName = "MongoDBStorage") - { - var connectionString = ConfigurationManager.ConnectionStrings[connectionStringName].ConnectionString; - var mongoUrl = MongoUrl.Create(connectionString); - var database = new MongoClient(mongoUrl).GetDatabase(mongoUrl.DatabaseName); - return new MongoDBStorage(database); - } } } diff --git a/_ReSharper.BuildRevisionCounter/PersistentCaches/MANIFEST-000013 b/_ReSharper.BuildRevisionCounter/PersistentCaches/MANIFEST-000013 new file mode 100644 index 0000000000000000000000000000000000000000..611565fdd7d9989bfaa5013308ae93495e5cc307 GIT binary patch literal 978 zcmd;c`JAlB#KlUkOVlai$8R9TW*o>`pgoS$2eSd>_jU&PM9y|s^NHY1}Mg9(Ex z7&$z13M7tUQLjDO Date: Thu, 22 Oct 2015 01:17:09 +0300 Subject: [PATCH 2/4] =?UTF-8?q?=D1=84=D1=83=D0=BD=D0=BA=D1=86=D0=B8=D0=BE?= =?UTF-8?q?=D0=BD=D0=B0=D0=BB=20IDataProvider'=D0=B0=20=D1=80=D0=B0=D0=B7?= =?UTF-8?q?=D0=B4=D0=B5=D0=BB=D0=B8=D0=BB=20=D0=BD=D0=B0=20=D0=B4=D0=B2?= =?UTF-8?q?=D0=B0=20=D0=B8=D0=BD=D1=82=D0=B5=D1=80=D1=84=D0=B5=D0=B9=D1=81?= =?UTF-8?q?=D0=B0,=20=D1=83=D0=B1=D1=80=D0=B0=D0=BB=20=D0=BA=D0=BB=D0=B0?= =?UTF-8?q?=D1=81=D1=81-=D0=BF=D1=80=D0=BE=D1=81=D0=BB=D0=BE=D0=B9=D0=BA?= =?UTF-8?q?=D1=83,=20=D0=B8=D1=81=D0=BF=D1=80=D0=B0=D0=B2=D0=B8=D0=BB=20?= =?UTF-8?q?=D1=81=D0=B2=D0=BE=D1=91=20=D0=BE=D1=88=D0=B8=D0=B1=D0=BE=D1=87?= =?UTF-8?q?=D0=BD=D0=BE=D0=B5=20=D1=84=D0=BE=D1=80=D0=BC=D0=B0=D1=82=D0=B8?= =?UTF-8?q?=D1=80=D0=B8=D0=B2=D0=B0=D0=BD=D0=B8=D0=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - разделил функционал на два блока: "рабочий" и "тестовый" (теперь отдельные интерфейсы) - убрал класс-прослойку, часть его функционала перенёс в расширяемые методы (extension) - в проекте тестирования создание DataProvider стало по классу - теперь возможно тестировать сразу несколько классов/несколько субд - поправил своё прошлое ошибочное форматирование (студия заменяла табы на пробелы - теперь настроил её правильно) - в gitignore файлы решарпера убрал с помощью "_ReSharper*" --- .gitignore | 5 +- .../Controllers/CounterControllerTest.cs | 5 +- .../DBStorageFactory.cs | 33 ++- BuildRevisionCounter.Tests/DBStorageTest.cs | 27 +-- .../IntegrationTest/IntegrationTest.cs | 10 +- .../BuildRevisionCounter.csproj | 3 +- .../Controllers/CounterController.cs | 23 +- BuildRevisionCounter/Data/DbProviderUtil.cs | 51 +++++ BuildRevisionCounter/Data/DbStorage.cs | 110 ---------- .../Data/DuplicateKeyException.cs | 8 +- BuildRevisionCounter/Data/MongoDBStorage.cs | 204 +++++++++--------- .../Interfaces/IDataProvider.cs | 25 +-- .../Interfaces/IDatabaseTestProvider.cs | 23 ++ .../Security/BasicAuthenticationFilter.cs | 16 +- BuildRevisionCounter/Startup.cs | 3 +- .../PersistentCaches/MANIFEST-000013 | Bin 978 -> 0 bytes 16 files changed, 260 insertions(+), 286 deletions(-) create mode 100644 BuildRevisionCounter/Data/DbProviderUtil.cs delete mode 100644 BuildRevisionCounter/Data/DbStorage.cs create mode 100644 BuildRevisionCounter/Interfaces/IDatabaseTestProvider.cs delete mode 100644 _ReSharper.BuildRevisionCounter/PersistentCaches/MANIFEST-000013 diff --git a/.gitignore b/.gitignore index 0a29cad..c8a30a3 100644 --- a/.gitignore +++ b/.gitignore @@ -11,7 +11,4 @@ TestResults TestResult.xml /packages/ /publish/ -*.bin -*.dat -*.sst -*.version +_ReSharper* diff --git a/BuildRevisionCounter.Tests/Controllers/CounterControllerTest.cs b/BuildRevisionCounter.Tests/Controllers/CounterControllerTest.cs index 756e1b7..de38c59 100644 --- a/BuildRevisionCounter.Tests/Controllers/CounterControllerTest.cs +++ b/BuildRevisionCounter.Tests/Controllers/CounterControllerTest.cs @@ -2,6 +2,7 @@ using System.Threading.Tasks; using System.Web.Http; using BuildRevisionCounter.Controllers; +using BuildRevisionCounter.Data; using NUnit.Framework; namespace BuildRevisionCounter.Tests.Controllers @@ -14,9 +15,9 @@ public class CounterControllerTest [TestFixtureSetUp] public void SetUp() { - DBStorageFactory.DefaultInstance.SetUpAsync().Wait(); + DBStorageFactory.DefaultInstance.SetUpAsync().Wait(); - _controller = new CounterController(DBStorageFactory.DefaultInstance); + _controller = new CounterController(DbProviderUtil.GetDataProvider()); } [Test] diff --git a/BuildRevisionCounter.Tests/DBStorageFactory.cs b/BuildRevisionCounter.Tests/DBStorageFactory.cs index 6e9505d..301eae4 100644 --- a/BuildRevisionCounter.Tests/DBStorageFactory.cs +++ b/BuildRevisionCounter.Tests/DBStorageFactory.cs @@ -1,23 +1,42 @@ using System; +using System.Configuration; using BuildRevisionCounter.Data; +using BuildRevisionCounter.Interfaces; namespace BuildRevisionCounter.Tests { public static class DBStorageFactory { - private static readonly Lazy _defaultInstance = - new Lazy(() => FromConfigurationConnectionString()); + private static readonly Lazy _defaultInstance = + new Lazy(() => FromConfigurationConnectionString()); - public static DbStorage DefaultInstance { get { return _defaultInstance.Value; } } + public static IDatabaseTestProvider DefaultInstance { get { return _defaultInstance.Value; } } - public static DbStorage FromConnectionString(string connectionStringName) + + public static IDatabaseTestProvider GetInstance(string connectionStringName = "MongoDBStorage") where T : IDatabaseTestProvider { - return new DbStorage(connectionStringName); + return GetDatabaseTestProvider(connectionStringName); } - public static DbStorage FromConfigurationConnectionString(string connectionStringName = "MongoDBStorage") + public static IDatabaseTestProvider FromConfigurationConnectionString(string connectionStringName = "MongoDBStorage") { - return FromConnectionString(connectionStringName); + return GetDatabaseTestProvider(connectionStringName); + } + + private static IDatabaseTestProvider GetDatabaseTestProvider(string connectionStringName) + { + const string typeName = "BuildRevisionCounter.Data.MongoDBStorage,BuildRevisionCounter"; + var type = Type.GetType(typeName); + if (type == null) + throw new ApplicationException("на найден класс для IDataProvider"); + var connectionString = ConfigurationManager.ConnectionStrings[connectionStringName].ConnectionString; + return (IDatabaseTestProvider)Activator.CreateInstance(type, connectionString); + } + + private static IDatabaseTestProvider GetDatabaseTestProvider(string connectionStringName) where T : IDatabaseTestProvider + { + var connectionString = ConfigurationManager.ConnectionStrings[connectionStringName].ConnectionString; + return (IDatabaseTestProvider)Activator.CreateInstance(typeof(T), connectionString); } } } \ No newline at end of file diff --git a/BuildRevisionCounter.Tests/DBStorageTest.cs b/BuildRevisionCounter.Tests/DBStorageTest.cs index 5a489a8..79121e7 100644 --- a/BuildRevisionCounter.Tests/DBStorageTest.cs +++ b/BuildRevisionCounter.Tests/DBStorageTest.cs @@ -1,5 +1,6 @@ using System.Threading.Tasks; using BuildRevisionCounter.Data; +using BuildRevisionCounter.Interfaces; using NUnit.Framework; namespace BuildRevisionCounter.Tests @@ -7,7 +8,7 @@ namespace BuildRevisionCounter.Tests [TestFixture] public class DBStorageTest { - private DbStorage _storage; + private IDatabaseTestProvider _storage; [TestFixtureSetUp] public void SetUp() @@ -17,17 +18,17 @@ public void SetUp() public async Task SetUpAsync() { - _storage = DBStorageFactory.DefaultInstance; - await _storage.SetUpAsync(); + _storage = DBStorageFactory.GetInstance(); + await _storage.SetUpAsync(); } [Test] public async Task EnsureAdminUserCreated() { - var adminName = _storage.GetAdminName(); - var user = await _storage.FindUser(adminName); - Assert.IsNotNull(user); - Assert.AreEqual(adminName, user.Name); + var adminName = _storage.AdminName; + var user = await _storage.FindUser(adminName); + Assert.IsNotNull(user); + Assert.AreEqual(adminName, user.Name); } [Test] @@ -61,16 +62,16 @@ public async Task EnsureAdminUserMayBeInvokedMultipleTimes() [Test] public async Task CreateUserMustThrowExceptionIfUserExists() { - const string userName = "CreateUserMustThrowExceptionIfUserExists"; - const string userPass = "CreateUserMustThrowExceptionIfUserExists"; - var userRole = new[] {"testRole"}; - await _storage.CreateUser(userName, userPass, userRole); + const string userName = "CreateUserMustThrowExceptionIfUserExists"; + const string userPass = "CreateUserMustThrowExceptionIfUserExists"; + var userRole = new[] {"testRole"}; + await _storage.CreateUser(userName, userPass, userRole); try { - await _storage.CreateUser(userName, userPass, userRole); + await _storage.CreateUser(userName, userPass, userRole); Assert.Fail(); } - catch (DuplicateKeyException) + catch (DuplicateKeyException) { } } diff --git a/BuildRevisionCounter.Tests/IntegrationTest/IntegrationTest.cs b/BuildRevisionCounter.Tests/IntegrationTest/IntegrationTest.cs index 1dfcaf6..0e313dd 100644 --- a/BuildRevisionCounter.Tests/IntegrationTest/IntegrationTest.cs +++ b/BuildRevisionCounter.Tests/IntegrationTest/IntegrationTest.cs @@ -6,6 +6,7 @@ using System.Net.Sockets; using System.Text; using System.Threading.Tasks; +using BuildRevisionCounter.Data; using Microsoft.Owin.Hosting; using NUnit.Framework; @@ -27,7 +28,8 @@ public void Setup() _uri = string.Format("http://localhost:{0}", port); _application = WebApp.Start(_uri); - DBStorageFactory.DefaultInstance.SetUpAsync().Wait(); + + DBStorageFactory.DefaultInstance.SetUpAsync().Wait(); } @@ -72,9 +74,9 @@ protected async Task SendPostRequest(string apiUri, string userName = "a string.Format("{0}:{1}", userName, password)))); var content = new FormUrlEncodedContent(new[] - { - new KeyValuePair("", "") - }); + { + new KeyValuePair("", "") + }); var responseMessage = await HttpClient.PostAsync(apiUri, content); return await responseMessage.Content.ReadAsStringAsync(); diff --git a/BuildRevisionCounter/BuildRevisionCounter.csproj b/BuildRevisionCounter/BuildRevisionCounter.csproj index c86bafd..9a7f9ae 100644 --- a/BuildRevisionCounter/BuildRevisionCounter.csproj +++ b/BuildRevisionCounter/BuildRevisionCounter.csproj @@ -124,8 +124,9 @@ - + + diff --git a/BuildRevisionCounter/Controllers/CounterController.cs b/BuildRevisionCounter/Controllers/CounterController.cs index cfbee0f..e1dea6f 100644 --- a/BuildRevisionCounter/Controllers/CounterController.cs +++ b/BuildRevisionCounter/Controllers/CounterController.cs @@ -3,6 +3,7 @@ using System.Net; using System.Threading.Tasks; using System.Web.Http; +using BuildRevisionCounter.Interfaces; using BuildRevisionCounter.Model; using BuildRevisionCounter.Security; using BuildRevisionCounter.Data; @@ -13,18 +14,18 @@ namespace BuildRevisionCounter.Controllers [BasicAuthentication] public class CounterController : ApiController { - private readonly DbStorage _dbStorage; + private readonly IDataProvider _dataProvider; /// /// Конструктор контроллера номеров ревизий. /// - /// Объект для получения данных из БД. - public CounterController(DbStorage dbStorage) + /// Объект для получения данных из БД. + public CounterController(IDataProvider dataProvider) { - if (dbStorage == null) - throw new ArgumentNullException("dbStorage"); + if (dataProvider == null) + throw new ArgumentNullException("dataProvider"); - _dbStorage = dbStorage; + _dataProvider = dataProvider; } [HttpGet] @@ -35,7 +36,7 @@ public async Task> GetAllRevision([FromUri] I if (pageSize < 1 || pageNumber < 1) throw new HttpResponseException(HttpStatusCode.BadRequest); - var revisions = await _dbStorage.GetAllRevision(pageSize, pageNumber); + var revisions = await _dataProvider.GetAllRevision(pageSize, pageNumber); if (revisions == null) throw new HttpResponseException(HttpStatusCode.NotFound); @@ -48,12 +49,12 @@ public async Task> GetAllRevision([FromUri] I [Authorize(Roles = "admin, editor, anonymous")] public async Task Current([FromUri] string revisionName) { - var revisionNumber = await _dbStorage.CurrentRevision(revisionName); + var revisionNumber = await _dataProvider.CurrentRevision(revisionName); - if (revisionNumber == null) + if (revisionNumber == null) throw new HttpResponseException(HttpStatusCode.NotFound); - return revisionNumber.Value; + return revisionNumber.Value; } [HttpPost] @@ -61,7 +62,7 @@ public async Task Current([FromUri] string revisionName) [Authorize(Roles = "buildserver")] public async Task Bumping([FromUri] string revisionName) { - return await _dbStorage.Bumping(revisionName); + return await _dataProvider.Bumping(revisionName); } } } \ No newline at end of file diff --git a/BuildRevisionCounter/Data/DbProviderUtil.cs b/BuildRevisionCounter/Data/DbProviderUtil.cs new file mode 100644 index 0000000..e2cac33 --- /dev/null +++ b/BuildRevisionCounter/Data/DbProviderUtil.cs @@ -0,0 +1,51 @@ +using System; +using System.Threading.Tasks; +using System.Configuration; +using BuildRevisionCounter.Interfaces; + +namespace BuildRevisionCounter.Data +{ + public static class DbProviderUtil + { + public static IDataProvider GetDataProvider(string connectionStringName = "MongoDBStorage") + { + const string typeName = "BuildRevisionCounter.Data.MongoDBStorage"; + var type = Type.GetType(typeName); + if (type == null) + throw new ApplicationException("на найден класс для IDataProvider"); + var connectionString = ConfigurationManager.ConnectionStrings[connectionStringName].ConnectionString; + return (IDataProvider)Activator.CreateInstance(type, connectionString); + } + + public static async Task Bumping(this IDataProvider dataProvider, string revisionName) + { + // попробуем обновить документ + var result = await dataProvider.FindOneAndUpdateRevisionModelAsync(revisionName); + if (result != null) + return result.CurrentNumber; + + // если не получилось, значит документ еще не был создан + // создадим его с начальным значением 0 + try + { + await dataProvider.RevisionInsertAsync(revisionName); + return 0; + } + catch (DuplicateKeyException) + { + } + + // если при вставке произошла ошибка значит мы не успели и запись там уже есть + // и теперь попытка обновления должна пройти без ошибок + result = await dataProvider.FindOneAndUpdateRevisionModelAsync(revisionName); + + return result.CurrentNumber; + } + + public static async Task SetUpAsync(this IDatabaseTestProvider dataProvider) + { + await dataProvider.DropDatabaseAsync(); + await dataProvider.SetUp(); + } + } +} \ No newline at end of file diff --git a/BuildRevisionCounter/Data/DbStorage.cs b/BuildRevisionCounter/Data/DbStorage.cs deleted file mode 100644 index 655b88c..0000000 --- a/BuildRevisionCounter/Data/DbStorage.cs +++ /dev/null @@ -1,110 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Threading.Tasks; -using System.Configuration; -using BuildRevisionCounter.Interfaces; -using BuildRevisionCounter.Model; - -namespace BuildRevisionCounter.Data -{ - public class DbStorage - { - private readonly IDataProvider _dataProvider; - - public DbStorage(string connectionStringName = "MongoDBStorage") - { - _dataProvider = GetDataProvider(connectionStringName); - } - - #region функционал сервиса - - private static IDataProvider GetDataProvider(string connectionStringName) - { - const string typeName = "BuildRevisionCounter.Data.MongoDBStorage"; - var type = Type.GetType(typeName); - if (type == null) - throw new ApplicationException("на найден класс для IDataProvider"); - var connectionString = ConfigurationManager.ConnectionStrings[connectionStringName].ConnectionString; - return (IDataProvider)Activator.CreateInstance(type, connectionString); - } - - public async Task> GetAllRevision(Int32 pageSize, Int32 pageNumber) - { - return await _dataProvider.GetAllRevision(pageSize, pageNumber); - } - - public async Task CurrentRevision(string revisionName) - { - return await _dataProvider.CurrentRevision(revisionName); - } - - public async Task Bumping(string revisionName) - { - // попробуем обновить документ - var result = await FindOneAndUpdateRevisionModelAsync(revisionName); - if (result != null) - return result.CurrentNumber; - - // если не получилось, значит документ еще не был создан - // создадим его с начальным значением 0 - try - { - await _dataProvider.RevisionInsertAsync(revisionName); - return 0; - } - catch (DuplicateKeyException) - { - } - - // если при вставке произошла ошибка значит мы не успели и запись там уже есть - // и теперь попытка обновления должна пройти без ошибок - result = await FindOneAndUpdateRevisionModelAsync(revisionName); - - return result.CurrentNumber; - } - - /// - /// Увеличивает счетчик в БД - /// - /// - /// - private async Task FindOneAndUpdateRevisionModelAsync(string revisionName) - { - return await _dataProvider.FindOneAndUpdateRevisionModelAsync(revisionName); - } - - #endregion - - public async Task SetUpAsync() - { - await _dataProvider.DropDatabaseAsync(); - await _dataProvider.SetUp(); - } - - #region операции с пользователями - - public string GetAdminName() - { - return _dataProvider.AdminName; - } - - public async Task FindUser(string name) - { - return await _dataProvider.FindUser(name); - } - - public async Task CreateUser(string name, string password, string[] roles) - { - await _dataProvider.CreateUser(name, password, roles); - } - - public async Task EnsureAdminUser() - { - await _dataProvider.EnsureAdminUser(); - } - - - - #endregion - } -} \ No newline at end of file diff --git a/BuildRevisionCounter/Data/DuplicateKeyException.cs b/BuildRevisionCounter/Data/DuplicateKeyException.cs index 370e3bb..5b8174f 100644 --- a/BuildRevisionCounter/Data/DuplicateKeyException.cs +++ b/BuildRevisionCounter/Data/DuplicateKeyException.cs @@ -2,8 +2,8 @@ namespace BuildRevisionCounter.Data { - [Serializable] - public class DuplicateKeyException : InvalidOperationException - { - } + [Serializable] + public class DuplicateKeyException : InvalidOperationException + { + } } \ No newline at end of file diff --git a/BuildRevisionCounter/Data/MongoDBStorage.cs b/BuildRevisionCounter/Data/MongoDBStorage.cs index 3d5a993..c7bbec1 100644 --- a/BuildRevisionCounter/Data/MongoDBStorage.cs +++ b/BuildRevisionCounter/Data/MongoDBStorage.cs @@ -7,24 +7,24 @@ namespace BuildRevisionCounter.Data { - public class MongoDBStorage : IDataProvider + public class MongoDBStorage : IDataProvider, IDatabaseTestProvider { - public string AdminName - { - get { return "admin"; } - } + public string AdminName + { + get { return "admin"; } + } public static readonly string AdminPassword = "admin"; public static readonly string[] AdminRoles = {"admin", "buildserver", "editor"}; - private readonly IMongoCollection _revisions; - private readonly IMongoCollection _users; + private readonly IMongoCollection _revisions; + private readonly IMongoCollection _users; public MongoDBStorage(string connectionString) { - var mongoUrl = MongoUrl.Create(connectionString); - var database = new MongoClient(mongoUrl).GetDatabase(mongoUrl.DatabaseName); - + var mongoUrl = MongoUrl.Create(connectionString); + var database = new MongoClient(mongoUrl).GetDatabase(mongoUrl.DatabaseName); + _revisions = database.GetCollection("revisions"); _users = database.GetCollection("users"); @@ -37,71 +37,71 @@ public async Task SetUp() await EnsureAdminUser(); } - public Task DropDatabaseAsync() - { - return _revisions.Database.Client.DropDatabaseAsync(_revisions.Database.DatabaseNamespace.DatabaseName); - } - - public async Task> GetAllRevision(Int32 pageSize, Int32 pageNumber) - { - var revisions = await _revisions - .Find(r => true) - .Skip(pageSize * (pageNumber - 1)) - .Limit(pageSize) - .ToListAsync(); - - return revisions; - } - - public async Task CurrentRevision(string revisionName) - { - var revision = await _revisions - .Find(r => r.Id == revisionName) - .SingleOrDefaultAsync(); - - if (revision == null) - return null; - - return revision.CurrentNumber; - } - - public async Task RevisionInsertAsync(string revisionName) - { - try - { - await _revisions - .InsertOneAsync(new RevisionModel - { - Id = revisionName, - CurrentNumber = 0, - Created = DateTime.UtcNow - }); - } - catch (MongoWriteException ex) - { - if (ex.WriteError != null && ex.WriteError.Category == ServerErrorCategory.DuplicateKey) - throw new DuplicateKeyException(); - throw; - } - } - - public async Task FindOneAndUpdateRevisionModelAsync(string revisionName) - { - var result = await _revisions - .FindOneAndUpdateAsync( - r => r.Id == revisionName, - Builders.Update - .Inc(r => r.CurrentNumber, 1) - .SetOnInsert(r => r.Created, DateTime.UtcNow) - .Set(r => r.Updated, DateTime.UtcNow), - new FindOneAndUpdateOptions - { - IsUpsert = false, - ReturnDocument = ReturnDocument.After - }); - - return result; - } + public Task DropDatabaseAsync() + { + return _revisions.Database.Client.DropDatabaseAsync(_revisions.Database.DatabaseNamespace.DatabaseName); + } + + public async Task> GetAllRevision(Int32 pageSize, Int32 pageNumber) + { + var revisions = await _revisions + .Find(r => true) + .Skip(pageSize * (pageNumber - 1)) + .Limit(pageSize) + .ToListAsync(); + + return revisions; + } + + public async Task CurrentRevision(string revisionName) + { + var revision = await _revisions + .Find(r => r.Id == revisionName) + .SingleOrDefaultAsync(); + + if (revision == null) + return null; + + return revision.CurrentNumber; + } + + public async Task RevisionInsertAsync(string revisionName) + { + try + { + await _revisions + .InsertOneAsync(new RevisionModel + { + Id = revisionName, + CurrentNumber = 0, + Created = DateTime.UtcNow + }); + } + catch (MongoWriteException ex) + { + if (ex.WriteError != null && ex.WriteError.Category == ServerErrorCategory.DuplicateKey) + throw new DuplicateKeyException(); + throw; + } + } + + public async Task FindOneAndUpdateRevisionModelAsync(string revisionName) + { + var result = await _revisions + .FindOneAndUpdateAsync( + r => r.Id == revisionName, + Builders.Update + .Inc(r => r.CurrentNumber, 1) + .SetOnInsert(r => r.Created, DateTime.UtcNow) + .Set(r => r.Updated, DateTime.UtcNow), + new FindOneAndUpdateOptions + { + IsUpsert = false, + ReturnDocument = ReturnDocument.After + }); + + return result; + } public async Task EnsureUsersIndex() { @@ -112,7 +112,7 @@ await _users.Indexes.CreateOneAsync( public async Task EnsureAdminUser() { - if (await _users.CountAsync(_ => true) == 0) + if (await _users.CountAsync(_ => true) == 0) { await CreateUser(AdminName, AdminPassword, AdminRoles); } @@ -120,36 +120,36 @@ public async Task EnsureAdminUser() public async Task FindUser(string name) { - return await _users.Find(u => u.Name == name).SingleOrDefaultAsync(); + return await _users.Find(u => u.Name == name).SingleOrDefaultAsync(); } - /// - /// создать пользователя - /// - /// имя - /// пароль - /// роли - /// - /// В случае дублирования имени пользователя + /// + /// создать пользователя + /// + /// имя + /// пароль + /// роли + /// + /// В случае дублирования имени пользователя public async Task CreateUser(string name, string password, string[] roles) { - try - { - await _users - .InsertOneAsync( - new UserModel - { - Name = name, - Password = password, - Roles = roles - }); - } - catch (MongoWriteException ex) - { - if (ex.WriteError != null && ex.WriteError.Category == ServerErrorCategory.DuplicateKey) - throw new DuplicateKeyException(); - throw; - } + try + { + await _users + .InsertOneAsync( + new UserModel + { + Name = name, + Password = password, + Roles = roles + }); + } + catch (MongoWriteException ex) + { + if (ex.WriteError != null && ex.WriteError.Category == ServerErrorCategory.DuplicateKey) + throw new DuplicateKeyException(); + throw; + } } } } \ No newline at end of file diff --git a/BuildRevisionCounter/Interfaces/IDataProvider.cs b/BuildRevisionCounter/Interfaces/IDataProvider.cs index 8958281..e4813f8 100644 --- a/BuildRevisionCounter/Interfaces/IDataProvider.cs +++ b/BuildRevisionCounter/Interfaces/IDataProvider.cs @@ -2,7 +2,6 @@ using System.Collections.Generic; using System.Threading.Tasks; using BuildRevisionCounter.Model; -using MongoDB.Driver; namespace BuildRevisionCounter.Interfaces { @@ -11,26 +10,14 @@ namespace BuildRevisionCounter.Interfaces /// public interface IDataProvider { - string AdminName { get; } + Task> GetAllRevision(Int32 pageSize, Int32 pageNumber); - Task> GetAllRevision(Int32 pageSize, Int32 pageNumber); + Task CurrentRevision(string revisionName); - Task CurrentRevision(string revisionName); + Task RevisionInsertAsync(string revisionName); - Task RevisionInsertAsync(string revisionName); + Task FindOneAndUpdateRevisionModelAsync(string revisionName); - Task FindOneAndUpdateRevisionModelAsync(string revisionName); - - Task SetUp(); - - Task DropDatabaseAsync(); - - Task FindUser(string name); - - - - Task CreateUser(string name, string password, string[] roles); - - Task EnsureAdminUser(); - } + Task FindUser(string name); + } } \ No newline at end of file diff --git a/BuildRevisionCounter/Interfaces/IDatabaseTestProvider.cs b/BuildRevisionCounter/Interfaces/IDatabaseTestProvider.cs new file mode 100644 index 0000000..2bd014b --- /dev/null +++ b/BuildRevisionCounter/Interfaces/IDatabaseTestProvider.cs @@ -0,0 +1,23 @@ +using System.Threading.Tasks; +using BuildRevisionCounter.Model; + +namespace BuildRevisionCounter.Interfaces +{ + /// + /// Интерфейс для проверки операций с БД. + /// + public interface IDatabaseTestProvider + { + string AdminName { get; } + + Task SetUp(); + + Task DropDatabaseAsync(); + + Task FindUser(string name); + + Task CreateUser(string name, string password, string[] roles); + + Task EnsureAdminUser(); + } +} \ No newline at end of file diff --git a/BuildRevisionCounter/Security/BasicAuthenticationFilter.cs b/BuildRevisionCounter/Security/BasicAuthenticationFilter.cs index 0c5a49f..e169796 100644 --- a/BuildRevisionCounter/Security/BasicAuthenticationFilter.cs +++ b/BuildRevisionCounter/Security/BasicAuthenticationFilter.cs @@ -6,7 +6,7 @@ using System.Threading; using System.Threading.Tasks; using System.Web.Http.Filters; -using BuildRevisionCounter.Data; +using BuildRevisionCounter.Interfaces; namespace BuildRevisionCounter.Security { @@ -27,18 +27,18 @@ public class BasicAuthenticationFilter : IAuthenticationFilter EncoderFallback.ExceptionFallback, DecoderFallback.ExceptionFallback); - private readonly DbStorage _dbStorage; + private readonly IDataProvider _dataProvider; /// /// Конструктор фильтра. /// - /// Объект для получения данных из БД. - public BasicAuthenticationFilter(DbStorage dbStorage) + /// Объект для получения данных из БД. + public BasicAuthenticationFilter(IDataProvider dataProvider) { - if (dbStorage == null) - throw new ArgumentNullException("dbStorage"); + if (dataProvider == null) + throw new ArgumentNullException("dataProvider"); - _dbStorage = dbStorage; + _dataProvider = dataProvider; } public string Realm { get; set; } @@ -87,7 +87,7 @@ public Task ChallengeAsync(HttpAuthenticationChallengeContext context, Cancellat private async Task Authenticate(string userName, string password) { IPrincipal principal = null; - var user = await _dbStorage.FindUser(userName); + var user = await _dataProvider.FindUser(userName); if (user != null && user.Password == password) { principal = new GenericPrincipal(new GenericIdentity(userName), user.Roles); diff --git a/BuildRevisionCounter/Startup.cs b/BuildRevisionCounter/Startup.cs index 5339283..201ef4c 100644 --- a/BuildRevisionCounter/Startup.cs +++ b/BuildRevisionCounter/Startup.cs @@ -1,5 +1,6 @@ using System.Web.Http.Filters; using BuildRevisionCounter.Data; +using BuildRevisionCounter.Interfaces; using BuildRevisionCounter.Security; using Microsoft.Owin; using Ninject; @@ -56,7 +57,7 @@ private static IKernel CreateKernel() /// Ядро Ninject. private static void RegisterServices(IKernel kernel) { - kernel.Bind().ToMethod(c => new DbStorage()).InSingletonScope(); + kernel.Bind().ToMethod(c => DbProviderUtil.GetDataProvider()).InSingletonScope(); kernel.BindHttpFilter(FilterScope.Controller).WhenControllerHas(); } } diff --git a/_ReSharper.BuildRevisionCounter/PersistentCaches/MANIFEST-000013 b/_ReSharper.BuildRevisionCounter/PersistentCaches/MANIFEST-000013 deleted file mode 100644 index 611565fdd7d9989bfaa5013308ae93495e5cc307..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 978 zcmd;c`JAlB#KlUkOVlai$8R9TW*o>`pgoS$2eSd>_jU&PM9y|s^NHY1}Mg9(Ex z7&$z13M7tUQLjDO Date: Sat, 24 Oct 2015 01:31:16 +0300 Subject: [PATCH 3/4] =?UTF-8?q?=D1=80=D0=B0=D0=B7=D0=B4=D0=B5=D0=BB=D0=B8?= =?UTF-8?q?=D0=BB=20"=D0=B1=D0=BE=D0=B5=D0=B2=D0=BE=D0=B9"=20=D0=B8=D0=BD?= =?UTF-8?q?=D1=82=D0=B5=D1=80=D1=84=D0=B5=D0=B9=D1=81,=20=D1=80=D0=B0?= =?UTF-8?q?=D0=B7=D0=B3=D1=80=D0=B0=D0=BD=D0=B8=D1=87=D0=B8=D0=BB=20=D0=B4?= =?UTF-8?q?=D0=BE=D1=81=D1=82=D1=83=D0=BF=20=D0=BA=20=D0=BC=D0=B5=D1=82?= =?UTF-8?q?=D0=BE=D0=B4=D0=B0=D0=BC=20=D1=82=D0=B5=D1=81=D1=82=D0=B8=D1=80?= =?UTF-8?q?=D0=BE=D0=B2=D0=B0=D0=BD=D0=B8=D1=8F=20=D0=91=D0=94,=20=D1=80?= =?UTF-8?q?=D0=B5=D1=84=D0=B0=D0=BA=D1=82=D0=BE=D1=80=D0=B8=D0=BD=D0=B3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - разделил "боевой" интерфейс на работу с ревизиями и с пользователями - разграничил доступ к методам тестирования БД (через атрибут с перечислением имён разрешенных для работу прорамм) - рефакторинг (пробелы, EnsureAdminUser, Bumping) --- .../Controllers/CounterControllerTest.cs | 2 +- .../DBStorageFactory.cs | 2 +- BuildRevisionCounter.Tests/DBStorageTest.cs | 2 +- .../BuildRevisionCounter.csproj | 4 +- .../Controllers/CounterController.cs | 4 +- BuildRevisionCounter/Data/DbProviderUtil.cs | 55 ++++++++++--------- BuildRevisionCounter/Data/MongoDBStorage.cs | 55 ++++++++++++------- .../Interfaces/IDatabaseTestProvider.cs | 4 -- ...taProvider.cs => IRevisionDataProvider.cs} | 8 +-- .../Interfaces/IUserDataProvider.cs | 12 ++++ .../Security/BasicAuthenticationFilter.cs | 4 +- .../Security/MethodUsageScopeAttribute.cs | 25 +++++++++ BuildRevisionCounter/Startup.cs | 3 +- 13 files changed, 115 insertions(+), 65 deletions(-) rename BuildRevisionCounter/Interfaces/{IDataProvider.cs => IRevisionDataProvider.cs} (67%) create mode 100644 BuildRevisionCounter/Interfaces/IUserDataProvider.cs create mode 100644 BuildRevisionCounter/Security/MethodUsageScopeAttribute.cs diff --git a/BuildRevisionCounter.Tests/Controllers/CounterControllerTest.cs b/BuildRevisionCounter.Tests/Controllers/CounterControllerTest.cs index de38c59..9dee62b 100644 --- a/BuildRevisionCounter.Tests/Controllers/CounterControllerTest.cs +++ b/BuildRevisionCounter.Tests/Controllers/CounterControllerTest.cs @@ -17,7 +17,7 @@ public void SetUp() { DBStorageFactory.DefaultInstance.SetUpAsync().Wait(); - _controller = new CounterController(DbProviderUtil.GetDataProvider()); + _controller = new CounterController(DbProviderUtil.GetRevisionDataProvider()); } [Test] diff --git a/BuildRevisionCounter.Tests/DBStorageFactory.cs b/BuildRevisionCounter.Tests/DBStorageFactory.cs index 301eae4..ef901c1 100644 --- a/BuildRevisionCounter.Tests/DBStorageFactory.cs +++ b/BuildRevisionCounter.Tests/DBStorageFactory.cs @@ -19,7 +19,7 @@ public static IDatabaseTestProvider GetInstance(string connectionStringName = } public static IDatabaseTestProvider FromConfigurationConnectionString(string connectionStringName = "MongoDBStorage") - { + { return GetDatabaseTestProvider(connectionStringName); } diff --git a/BuildRevisionCounter.Tests/DBStorageTest.cs b/BuildRevisionCounter.Tests/DBStorageTest.cs index 79121e7..7523a5d 100644 --- a/BuildRevisionCounter.Tests/DBStorageTest.cs +++ b/BuildRevisionCounter.Tests/DBStorageTest.cs @@ -25,7 +25,7 @@ public async Task SetUpAsync() [Test] public async Task EnsureAdminUserCreated() { - var adminName = _storage.AdminName; + var adminName = _storage.GetAdminName(); var user = await _storage.FindUser(adminName); Assert.IsNotNull(user); Assert.AreEqual(adminName, user.Name); diff --git a/BuildRevisionCounter/BuildRevisionCounter.csproj b/BuildRevisionCounter/BuildRevisionCounter.csproj index 9a7f9ae..2441954 100644 --- a/BuildRevisionCounter/BuildRevisionCounter.csproj +++ b/BuildRevisionCounter/BuildRevisionCounter.csproj @@ -127,7 +127,8 @@ - + + @@ -136,6 +137,7 @@ + diff --git a/BuildRevisionCounter/Controllers/CounterController.cs b/BuildRevisionCounter/Controllers/CounterController.cs index e1dea6f..83059ee 100644 --- a/BuildRevisionCounter/Controllers/CounterController.cs +++ b/BuildRevisionCounter/Controllers/CounterController.cs @@ -14,13 +14,13 @@ namespace BuildRevisionCounter.Controllers [BasicAuthentication] public class CounterController : ApiController { - private readonly IDataProvider _dataProvider; + private readonly IRevisionDataProvider _dataProvider; /// /// Конструктор контроллера номеров ревизий. /// /// Объект для получения данных из БД. - public CounterController(IDataProvider dataProvider) + public CounterController(IRevisionDataProvider dataProvider) { if (dataProvider == null) throw new ArgumentNullException("dataProvider"); diff --git a/BuildRevisionCounter/Data/DbProviderUtil.cs b/BuildRevisionCounter/Data/DbProviderUtil.cs index e2cac33..cb66f74 100644 --- a/BuildRevisionCounter/Data/DbProviderUtil.cs +++ b/BuildRevisionCounter/Data/DbProviderUtil.cs @@ -7,45 +7,48 @@ namespace BuildRevisionCounter.Data { public static class DbProviderUtil { - public static IDataProvider GetDataProvider(string connectionStringName = "MongoDBStorage") + + public static string GetAdminName(this IDatabaseTestProvider dataProvider) + { + return "admin"; + } + + private const string AdminPassword = "admin"; + + private static readonly string[] AdminRoles = { "admin", "buildserver", "editor" }; + + public static IRevisionDataProvider GetRevisionDataProvider(string connectionStringName = "MongoDBStorage") { const string typeName = "BuildRevisionCounter.Data.MongoDBStorage"; var type = Type.GetType(typeName); if (type == null) throw new ApplicationException("на найден класс для IDataProvider"); var connectionString = ConfigurationManager.ConnectionStrings[connectionStringName].ConnectionString; - return (IDataProvider)Activator.CreateInstance(type, connectionString); + return (IRevisionDataProvider)Activator.CreateInstance(type, connectionString); } - - public static async Task Bumping(this IDataProvider dataProvider, string revisionName) - { - // попробуем обновить документ - var result = await dataProvider.FindOneAndUpdateRevisionModelAsync(revisionName); - if (result != null) - return result.CurrentNumber; - - // если не получилось, значит документ еще не был создан - // создадим его с начальным значением 0 - try - { - await dataProvider.RevisionInsertAsync(revisionName); - return 0; - } - catch (DuplicateKeyException) - { - } - - // если при вставке произошла ошибка значит мы не успели и запись там уже есть - // и теперь попытка обновления должна пройти без ошибок - result = await dataProvider.FindOneAndUpdateRevisionModelAsync(revisionName); - return result.CurrentNumber; + public static IUserDataProvider GetUserDataProvider(string connectionStringName = "MongoDBStorage") + { + const string typeName = "BuildRevisionCounter.Data.MongoDBStorage"; + var type = Type.GetType(typeName); + if (type == null) + throw new ApplicationException("на найден класс для IDataProvider"); + var connectionString = ConfigurationManager.ConnectionStrings[connectionStringName].ConnectionString; + return (IUserDataProvider)Activator.CreateInstance(type, connectionString); } - + public static async Task SetUpAsync(this IDatabaseTestProvider dataProvider) { await dataProvider.DropDatabaseAsync(); await dataProvider.SetUp(); } + + public static async Task EnsureAdminUser(this IDatabaseTestProvider dataProvider) + { + if (await dataProvider.FindUser(dataProvider.GetAdminName()) == null) + { + await dataProvider.CreateUser(dataProvider.GetAdminName(), AdminPassword, AdminRoles); + } + } } } \ No newline at end of file diff --git a/BuildRevisionCounter/Data/MongoDBStorage.cs b/BuildRevisionCounter/Data/MongoDBStorage.cs index c7bbec1..7d64a52 100644 --- a/BuildRevisionCounter/Data/MongoDBStorage.cs +++ b/BuildRevisionCounter/Data/MongoDBStorage.cs @@ -1,22 +1,16 @@ using System; using System.Collections.Generic; +using System.Reflection; using System.Threading.Tasks; using BuildRevisionCounter.Interfaces; using BuildRevisionCounter.Model; +using BuildRevisionCounter.Security; using MongoDB.Driver; namespace BuildRevisionCounter.Data { - public class MongoDBStorage : IDataProvider, IDatabaseTestProvider + public class MongoDBStorage : IRevisionDataProvider, IUserDataProvider, IDatabaseTestProvider { - public string AdminName - { - get { return "admin"; } - } - - public static readonly string AdminPassword = "admin"; - public static readonly string[] AdminRoles = {"admin", "buildserver", "editor"}; - private readonly IMongoCollection _revisions; private readonly IMongoCollection _users; @@ -31,14 +25,18 @@ public MongoDBStorage(string connectionString) Task.Run(() => SetUp()).Wait(); } + [MethodUsageScope(MainModuleName = "nunit-agent.exe,nunit.exe,nunit.exe,nunit-x86.exe")] public async Task SetUp() { + MethodUsageScopeAttribute.ControlScope(MethodBase.GetCurrentMethod()); await EnsureUsersIndex(); - await EnsureAdminUser(); + await this.EnsureAdminUser(); } + [MethodUsageScope(MainModuleName = "nunit-agent.exe,nunit.exe,nunit-x86.exe")] public Task DropDatabaseAsync() { + MethodUsageScopeAttribute.ControlScope(MethodBase.GetCurrentMethod()); return _revisions.Database.Client.DropDatabaseAsync(_revisions.Database.DatabaseNamespace.DatabaseName); } @@ -65,7 +63,32 @@ public async Task> GetAllRevision(Int32 pageS return revision.CurrentNumber; } - public async Task RevisionInsertAsync(string revisionName) + public async Task Bumping(string revisionName) + { + // попробуем обновить документ + var result = await FindOneAndUpdateRevisionModelAsync(revisionName); + if (result != null) + return result.CurrentNumber; + + // если не получилось, значит документ еще не был создан + // создадим его с начальным значением 0 + try + { + await RevisionInsertAsync(revisionName); + return 0; + } + catch (DuplicateKeyException) + { + } + + // если при вставке произошла ошибка значит мы не успели и запись там уже есть + // и теперь попытка обновления должна пройти без ошибок + result = await FindOneAndUpdateRevisionModelAsync(revisionName); + + return result.CurrentNumber; + } + + private async Task RevisionInsertAsync(string revisionName) { try { @@ -85,7 +108,7 @@ await _revisions } } - public async Task FindOneAndUpdateRevisionModelAsync(string revisionName) + private async Task FindOneAndUpdateRevisionModelAsync(string revisionName) { var result = await _revisions .FindOneAndUpdateAsync( @@ -110,14 +133,6 @@ await _users.Indexes.CreateOneAsync( new CreateIndexOptions { Unique = true, }); } - public async Task EnsureAdminUser() - { - if (await _users.CountAsync(_ => true) == 0) - { - await CreateUser(AdminName, AdminPassword, AdminRoles); - } - } - public async Task FindUser(string name) { return await _users.Find(u => u.Name == name).SingleOrDefaultAsync(); diff --git a/BuildRevisionCounter/Interfaces/IDatabaseTestProvider.cs b/BuildRevisionCounter/Interfaces/IDatabaseTestProvider.cs index 2bd014b..b889297 100644 --- a/BuildRevisionCounter/Interfaces/IDatabaseTestProvider.cs +++ b/BuildRevisionCounter/Interfaces/IDatabaseTestProvider.cs @@ -8,8 +8,6 @@ namespace BuildRevisionCounter.Interfaces /// public interface IDatabaseTestProvider { - string AdminName { get; } - Task SetUp(); Task DropDatabaseAsync(); @@ -17,7 +15,5 @@ public interface IDatabaseTestProvider Task FindUser(string name); Task CreateUser(string name, string password, string[] roles); - - Task EnsureAdminUser(); } } \ No newline at end of file diff --git a/BuildRevisionCounter/Interfaces/IDataProvider.cs b/BuildRevisionCounter/Interfaces/IRevisionDataProvider.cs similarity index 67% rename from BuildRevisionCounter/Interfaces/IDataProvider.cs rename to BuildRevisionCounter/Interfaces/IRevisionDataProvider.cs index e4813f8..8c54395 100644 --- a/BuildRevisionCounter/Interfaces/IDataProvider.cs +++ b/BuildRevisionCounter/Interfaces/IRevisionDataProvider.cs @@ -8,16 +8,12 @@ namespace BuildRevisionCounter.Interfaces /// /// Интерфейс для получения данных из БД. /// - public interface IDataProvider + public interface IRevisionDataProvider { Task> GetAllRevision(Int32 pageSize, Int32 pageNumber); Task CurrentRevision(string revisionName); - Task RevisionInsertAsync(string revisionName); - - Task FindOneAndUpdateRevisionModelAsync(string revisionName); - - Task FindUser(string name); + Task Bumping(string revisionName); } } \ No newline at end of file diff --git a/BuildRevisionCounter/Interfaces/IUserDataProvider.cs b/BuildRevisionCounter/Interfaces/IUserDataProvider.cs new file mode 100644 index 0000000..08f8417 --- /dev/null +++ b/BuildRevisionCounter/Interfaces/IUserDataProvider.cs @@ -0,0 +1,12 @@ +using System.Threading.Tasks; +using BuildRevisionCounter.Model; + +namespace BuildRevisionCounter.Interfaces +{ + public interface IUserDataProvider + { + Task FindUser(string name); + + Task CreateUser(string name, string password, string[] roles); + } +} \ No newline at end of file diff --git a/BuildRevisionCounter/Security/BasicAuthenticationFilter.cs b/BuildRevisionCounter/Security/BasicAuthenticationFilter.cs index e169796..0ba43ba 100644 --- a/BuildRevisionCounter/Security/BasicAuthenticationFilter.cs +++ b/BuildRevisionCounter/Security/BasicAuthenticationFilter.cs @@ -27,13 +27,13 @@ public class BasicAuthenticationFilter : IAuthenticationFilter EncoderFallback.ExceptionFallback, DecoderFallback.ExceptionFallback); - private readonly IDataProvider _dataProvider; + private readonly IUserDataProvider _dataProvider; /// /// Конструктор фильтра. /// /// Объект для получения данных из БД. - public BasicAuthenticationFilter(IDataProvider dataProvider) + public BasicAuthenticationFilter(IUserDataProvider dataProvider) { if (dataProvider == null) throw new ArgumentNullException("dataProvider"); diff --git a/BuildRevisionCounter/Security/MethodUsageScopeAttribute.cs b/BuildRevisionCounter/Security/MethodUsageScopeAttribute.cs new file mode 100644 index 0000000..b55faf3 --- /dev/null +++ b/BuildRevisionCounter/Security/MethodUsageScopeAttribute.cs @@ -0,0 +1,25 @@ +using System; +using System.Reflection; + +namespace BuildRevisionCounter.Security +{ + public class MethodUsageScopeAttribute: Attribute + { + public string MainModuleName { get; set; } + + public static void ControlScope(MemberInfo methodBase) + { + var att = (MethodUsageScopeAttribute)GetCustomAttribute(methodBase, typeof(MethodUsageScopeAttribute)); + if (att == null || att.MainModuleName == null) + return; + var allowNames = att.MainModuleName.ToLower().Split(','); + var moduleName = System.Diagnostics.Process.GetCurrentProcess().MainModule.ModuleName.ToLower(); + foreach (var name in allowNames) + { + if (name == moduleName) + return; + } + throw new InvalidOperationException(); + } + } +} \ No newline at end of file diff --git a/BuildRevisionCounter/Startup.cs b/BuildRevisionCounter/Startup.cs index 201ef4c..a732cfa 100644 --- a/BuildRevisionCounter/Startup.cs +++ b/BuildRevisionCounter/Startup.cs @@ -57,7 +57,8 @@ private static IKernel CreateKernel() /// Ядро Ninject. private static void RegisterServices(IKernel kernel) { - kernel.Bind().ToMethod(c => DbProviderUtil.GetDataProvider()).InSingletonScope(); + kernel.Bind().ToMethod(c => DbProviderUtil.GetRevisionDataProvider()).InSingletonScope(); + kernel.Bind().ToMethod(c => DbProviderUtil.GetUserDataProvider()).InSingletonScope(); kernel.BindHttpFilter(FilterScope.Controller).WhenControllerHas(); } } From d6d568842268ac6125993e366627ed7104ec2bc7 Mon Sep 17 00:00:00 2001 From: sFedyashov Date: Tue, 27 Oct 2015 02:32:44 +0300 Subject: [PATCH 4/4] =?UTF-8?q?=D0=BF=D1=80=D0=B0=D0=B2=D0=BA=D0=B8=20?= =?UTF-8?q?=D0=BF=D0=BE=20=D0=B7=D0=B0=D0=BC=D0=B5=D1=87=D0=B0=D0=BD=D0=B8?= =?UTF-8?q?=D1=8F=D0=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit следующие правки: -разделение класса работы с БД на 2 (ревизии и пользователи) -код удаления БД перенесён в тестирующую сборку -для тестов создаётся БД со случайным именем (..._GUID), после тестов удаляется. -переименования интерфейсов --- .../BuildRevisionCounter.Tests.csproj | 1 + .../Controllers/CounterControllerTest.cs | 11 ++- .../DBStorageFactory.cs | 24 +++--- BuildRevisionCounter.Tests/DBStorageTest.cs | 12 ++- BuildRevisionCounter.Tests/DBUtil.cs | 62 ++++++++++++++ .../IntegrationTest/IntegrationTest.cs | 21 ++++- .../BuildRevisionCounter.csproj | 12 +-- .../Controllers/CounterController.cs | 18 ++-- BuildRevisionCounter/Data/DBStorageUtil.cs | 39 +++++++++ BuildRevisionCounter/Data/DbProviderUtil.cs | 54 ------------ ...DBStorage.cs => MongoDBRevisionStorage.cs} | 85 +++---------------- .../Data/MongoDBUserStorage.cs | 73 ++++++++++++++++ ...ionDataProvider.cs => IRevisionStorage.cs} | 2 +- ...ovider.cs => IUserDatabaseTestProvider.cs} | 10 +-- .../{IUserDataProvider.cs => IUserStorage.cs} | 2 +- .../Security/BasicAuthenticationFilter.cs | 14 +-- .../Security/MethodUsageScopeAttribute.cs | 25 ------ BuildRevisionCounter/Startup.cs | 4 +- 18 files changed, 265 insertions(+), 204 deletions(-) create mode 100644 BuildRevisionCounter.Tests/DBUtil.cs create mode 100644 BuildRevisionCounter/Data/DBStorageUtil.cs delete mode 100644 BuildRevisionCounter/Data/DbProviderUtil.cs rename BuildRevisionCounter/Data/{MongoDBStorage.cs => MongoDBRevisionStorage.cs} (55%) create mode 100644 BuildRevisionCounter/Data/MongoDBUserStorage.cs rename BuildRevisionCounter/Interfaces/{IRevisionDataProvider.cs => IRevisionStorage.cs} (92%) rename BuildRevisionCounter/Interfaces/{IDatabaseTestProvider.cs => IUserDatabaseTestProvider.cs} (79%) rename BuildRevisionCounter/Interfaces/{IUserDataProvider.cs => IUserStorage.cs} (86%) delete mode 100644 BuildRevisionCounter/Security/MethodUsageScopeAttribute.cs diff --git a/BuildRevisionCounter.Tests/BuildRevisionCounter.Tests.csproj b/BuildRevisionCounter.Tests/BuildRevisionCounter.Tests.csproj index ffa366b..478d03e 100644 --- a/BuildRevisionCounter.Tests/BuildRevisionCounter.Tests.csproj +++ b/BuildRevisionCounter.Tests/BuildRevisionCounter.Tests.csproj @@ -92,6 +92,7 @@ + diff --git a/BuildRevisionCounter.Tests/Controllers/CounterControllerTest.cs b/BuildRevisionCounter.Tests/Controllers/CounterControllerTest.cs index 9dee62b..c6e7eaf 100644 --- a/BuildRevisionCounter.Tests/Controllers/CounterControllerTest.cs +++ b/BuildRevisionCounter.Tests/Controllers/CounterControllerTest.cs @@ -15,9 +15,8 @@ public class CounterControllerTest [TestFixtureSetUp] public void SetUp() { - DBStorageFactory.DefaultInstance.SetUpAsync().Wait(); - - _controller = new CounterController(DbProviderUtil.GetRevisionDataProvider()); + DBStorageFactory.DefaultInstance.SetUp().Wait(); + _controller = new CounterController(DBStorageUtil.GetRevisionStorage(connectionString: DBStorageFactory.DefaultInstance.ConnectionString)); } [Test] @@ -56,5 +55,11 @@ public async Task CurrentReturnsSameValueAsPreviousBumping() var rev2 = await _controller.Current("CurrentReturnSameValueAsPreviousBumping"); Assert.AreEqual(rev1, rev2); } + + [TestFixtureTearDown] + public void DropDatabaseAsync() + { + DBStorageFactory.DefaultInstance.DropDatabaseAsync().Wait(); + } } } \ No newline at end of file diff --git a/BuildRevisionCounter.Tests/DBStorageFactory.cs b/BuildRevisionCounter.Tests/DBStorageFactory.cs index ef901c1..3945217 100644 --- a/BuildRevisionCounter.Tests/DBStorageFactory.cs +++ b/BuildRevisionCounter.Tests/DBStorageFactory.cs @@ -7,36 +7,34 @@ namespace BuildRevisionCounter.Tests { public static class DBStorageFactory { - private static readonly Lazy _defaultInstance = - new Lazy(() => FromConfigurationConnectionString()); + private static readonly Lazy _defaultInstance = + new Lazy(() => FromConfigurationConnectionString()); - public static IDatabaseTestProvider DefaultInstance { get { return _defaultInstance.Value; } } + public static IUserDatabaseTestProvider DefaultInstance { get { return _defaultInstance.Value; } } - public static IDatabaseTestProvider GetInstance(string connectionStringName = "MongoDBStorage") where T : IDatabaseTestProvider + public static IUserDatabaseTestProvider GetInstance(string connectionStringName = "MongoDBStorage") where T : IUserDatabaseTestProvider { return GetDatabaseTestProvider(connectionStringName); } - public static IDatabaseTestProvider FromConfigurationConnectionString(string connectionStringName = "MongoDBStorage") + public static IUserDatabaseTestProvider FromConfigurationConnectionString(string connectionStringName = "MongoDBStorage") { return GetDatabaseTestProvider(connectionStringName); } - private static IDatabaseTestProvider GetDatabaseTestProvider(string connectionStringName) + private static IUserDatabaseTestProvider GetDatabaseTestProvider(string connectionStringName) { - const string typeName = "BuildRevisionCounter.Data.MongoDBStorage,BuildRevisionCounter"; - var type = Type.GetType(typeName); - if (type == null) - throw new ApplicationException("на найден класс для IDataProvider"); var connectionString = ConfigurationManager.ConnectionStrings[connectionStringName].ConnectionString; - return (IDatabaseTestProvider)Activator.CreateInstance(type, connectionString); + connectionString = DBUtil.SetDatabaseNameRandom(typeof(MongoDBUserStorage), connectionString); + return new MongoDBUserStorage(connectionString); } - private static IDatabaseTestProvider GetDatabaseTestProvider(string connectionStringName) where T : IDatabaseTestProvider + private static IUserDatabaseTestProvider GetDatabaseTestProvider(string connectionStringName) where T : IUserDatabaseTestProvider { var connectionString = ConfigurationManager.ConnectionStrings[connectionStringName].ConnectionString; - return (IDatabaseTestProvider)Activator.CreateInstance(typeof(T), connectionString); + connectionString = DBUtil.SetDatabaseNameRandom(typeof (T), connectionString); + return (IUserDatabaseTestProvider)Activator.CreateInstance(typeof(T), connectionString); } } } \ No newline at end of file diff --git a/BuildRevisionCounter.Tests/DBStorageTest.cs b/BuildRevisionCounter.Tests/DBStorageTest.cs index 7523a5d..62b28d0 100644 --- a/BuildRevisionCounter.Tests/DBStorageTest.cs +++ b/BuildRevisionCounter.Tests/DBStorageTest.cs @@ -8,7 +8,7 @@ namespace BuildRevisionCounter.Tests [TestFixture] public class DBStorageTest { - private IDatabaseTestProvider _storage; + private IUserDatabaseTestProvider _storage; [TestFixtureSetUp] public void SetUp() @@ -18,8 +18,8 @@ public void SetUp() public async Task SetUpAsync() { - _storage = DBStorageFactory.GetInstance(); - await _storage.SetUpAsync(); + _storage = DBStorageFactory.GetInstance(); + await _storage.SetUp(); } [Test] @@ -75,5 +75,11 @@ public async Task CreateUserMustThrowExceptionIfUserExists() { } } + + [TestFixtureTearDown] + public void DropDatabaseAsync() + { + _storage.DropDatabaseAsync().Wait(); + } } } \ No newline at end of file diff --git a/BuildRevisionCounter.Tests/DBUtil.cs b/BuildRevisionCounter.Tests/DBUtil.cs new file mode 100644 index 0000000..57ad5a9 --- /dev/null +++ b/BuildRevisionCounter.Tests/DBUtil.cs @@ -0,0 +1,62 @@ +using System; +using System.Threading.Tasks; +using BuildRevisionCounter.Data; +using BuildRevisionCounter.Interfaces; +using MongoDB.Driver; + +namespace BuildRevisionCounter.Tests +{ + internal static class DBUtil + { + internal static Task DropDatabaseAsync(this IUserDatabaseTestProvider dataProvider) + { + switch (GetDatabaseKind(dataProvider.GetType())) + { + case DatabaseKind.MongoDB: + return MangoDBDropDatabaseAsync(dataProvider.ConnectionString); + default: + throw new NotImplementedException(); + } + } + + internal static string SetDatabaseNameRandom(Type storageType, string connectionString) + { + switch (GetDatabaseKind(storageType)) + { + case DatabaseKind.MongoDB: + return MangoDBSetDatabaseNameRandom(connectionString); + default: + throw new NotImplementedException(); + } + } + + private static string MangoDBSetDatabaseNameRandom(string connectionString) + { + var urlBuilder = new MongoUrlBuilder(connectionString) + { + DatabaseName = "brCounterTest_" + Guid.NewGuid().ToString("N") + }; + return urlBuilder.ToString(); + } + + private static Task MangoDBDropDatabaseAsync(string connectionString) + { + var mongoUrl = MongoUrl.Create(connectionString); + return new MongoClient(mongoUrl).DropDatabaseAsync(mongoUrl.DatabaseName); + } + + + private enum DatabaseKind + { + Unknow, + MongoDB + } + + private static DatabaseKind GetDatabaseKind(Type storageType) + { + if (storageType == typeof(MongoDBUserStorage)) + return DatabaseKind.MongoDB; + return DatabaseKind.Unknow; + } + } +} diff --git a/BuildRevisionCounter.Tests/IntegrationTest/IntegrationTest.cs b/BuildRevisionCounter.Tests/IntegrationTest/IntegrationTest.cs index 0e313dd..fa37151 100644 --- a/BuildRevisionCounter.Tests/IntegrationTest/IntegrationTest.cs +++ b/BuildRevisionCounter.Tests/IntegrationTest/IntegrationTest.cs @@ -1,9 +1,11 @@ using System; using System.Collections.Generic; +using System.Configuration; using System.Net; using System.Net.Http; using System.Net.Http.Headers; using System.Net.Sockets; +using System.Reflection; using System.Text; using System.Threading.Tasks; using BuildRevisionCounter.Data; @@ -24,19 +26,28 @@ public abstract class IntegrationTest [TestFixtureSetUp] public void Setup() { + ChangeConnectionStringInConfigurationManager("MongoDBStorage", DBStorageFactory.DefaultInstance.ConnectionString); + var port = GetFreeTcpPort(); _uri = string.Format("http://localhost:{0}", port); _application = WebApp.Start(_uri); - - DBStorageFactory.DefaultInstance.SetUpAsync().Wait(); + DBStorageFactory.DefaultInstance.SetUp().Wait(); } + private static void ChangeConnectionStringInConfigurationManager(string connectionStringName, string connectionString) + { + var settings = ConfigurationManager.ConnectionStrings[connectionStringName]; + var fi = typeof (ConfigurationElement).GetField("_bReadOnly", BindingFlags.Instance | BindingFlags.NonPublic); + fi.SetValue(settings, false); + settings.ConnectionString = connectionString; + } [TestFixtureTearDown] public void TearDown() { _application.Dispose(); + DBStorageFactory.DefaultInstance.DropDatabaseAsync().Wait(); } private static int GetFreeTcpPort() @@ -82,6 +93,12 @@ protected async Task SendPostRequest(string apiUri, string userName = "a return await responseMessage.Content.ReadAsStringAsync(); } } + + [TestFixtureTearDown] + public void DropDatabaseAsync() + { + DBStorageFactory.DefaultInstance.DropDatabaseAsync().Wait(); + } } } diff --git a/BuildRevisionCounter/BuildRevisionCounter.csproj b/BuildRevisionCounter/BuildRevisionCounter.csproj index 2441954..c3bc259 100644 --- a/BuildRevisionCounter/BuildRevisionCounter.csproj +++ b/BuildRevisionCounter/BuildRevisionCounter.csproj @@ -124,20 +124,20 @@ - + - - - + + + + - + - diff --git a/BuildRevisionCounter/Controllers/CounterController.cs b/BuildRevisionCounter/Controllers/CounterController.cs index 83059ee..bf8df86 100644 --- a/BuildRevisionCounter/Controllers/CounterController.cs +++ b/BuildRevisionCounter/Controllers/CounterController.cs @@ -14,18 +14,18 @@ namespace BuildRevisionCounter.Controllers [BasicAuthentication] public class CounterController : ApiController { - private readonly IRevisionDataProvider _dataProvider; + private readonly IRevisionStorage _dataStorage; /// /// Конструктор контроллера номеров ревизий. /// - /// Объект для получения данных из БД. - public CounterController(IRevisionDataProvider dataProvider) + /// Объект для получения данных из БД. + public CounterController(IRevisionStorage dataStorage) { - if (dataProvider == null) - throw new ArgumentNullException("dataProvider"); + if (dataStorage == null) + throw new ArgumentNullException("dataStorage"); - _dataProvider = dataProvider; + _dataStorage = dataStorage; } [HttpGet] @@ -36,7 +36,7 @@ public async Task> GetAllRevision([FromUri] I if (pageSize < 1 || pageNumber < 1) throw new HttpResponseException(HttpStatusCode.BadRequest); - var revisions = await _dataProvider.GetAllRevision(pageSize, pageNumber); + var revisions = await _dataStorage.GetAllRevision(pageSize, pageNumber); if (revisions == null) throw new HttpResponseException(HttpStatusCode.NotFound); @@ -49,7 +49,7 @@ public async Task> GetAllRevision([FromUri] I [Authorize(Roles = "admin, editor, anonymous")] public async Task Current([FromUri] string revisionName) { - var revisionNumber = await _dataProvider.CurrentRevision(revisionName); + var revisionNumber = await _dataStorage.CurrentRevision(revisionName); if (revisionNumber == null) throw new HttpResponseException(HttpStatusCode.NotFound); @@ -62,7 +62,7 @@ public async Task Current([FromUri] string revisionName) [Authorize(Roles = "buildserver")] public async Task Bumping([FromUri] string revisionName) { - return await _dataProvider.Bumping(revisionName); + return await _dataStorage.Bumping(revisionName); } } } \ No newline at end of file diff --git a/BuildRevisionCounter/Data/DBStorageUtil.cs b/BuildRevisionCounter/Data/DBStorageUtil.cs new file mode 100644 index 0000000..e73e97e --- /dev/null +++ b/BuildRevisionCounter/Data/DBStorageUtil.cs @@ -0,0 +1,39 @@ +using System.Threading.Tasks; +using System.Configuration; +using BuildRevisionCounter.Interfaces; + +namespace BuildRevisionCounter.Data +{ + public static class DBStorageUtil + { + + public static string GetAdminName(this IUserDatabaseTestProvider dataProvider) + { + return "admin"; + } + + private const string AdminPassword = "admin"; + + private static readonly string[] AdminRoles = { "admin", "buildserver", "editor" }; + + public static IRevisionStorage GetRevisionStorage(string connectionStringName = "MongoDBStorage", string connectionString = null) + { + connectionString = connectionString ?? ConfigurationManager.ConnectionStrings[connectionStringName].ConnectionString; + return new MongoDBRevisionStorage(connectionString); + } + + public static IUserStorage GetUserStorage(string connectionStringName = "MongoDBStorage") + { + var connectionString = ConfigurationManager.ConnectionStrings[connectionStringName].ConnectionString; + return new MongoDBUserStorage(connectionString); + } + + public static async Task EnsureAdminUser(this IUserDatabaseTestProvider dataProvider) + { + if (await dataProvider.FindUser(dataProvider.GetAdminName()) == null) + { + await dataProvider.CreateUser(dataProvider.GetAdminName(), AdminPassword, AdminRoles); + } + } + } +} \ No newline at end of file diff --git a/BuildRevisionCounter/Data/DbProviderUtil.cs b/BuildRevisionCounter/Data/DbProviderUtil.cs deleted file mode 100644 index cb66f74..0000000 --- a/BuildRevisionCounter/Data/DbProviderUtil.cs +++ /dev/null @@ -1,54 +0,0 @@ -using System; -using System.Threading.Tasks; -using System.Configuration; -using BuildRevisionCounter.Interfaces; - -namespace BuildRevisionCounter.Data -{ - public static class DbProviderUtil - { - - public static string GetAdminName(this IDatabaseTestProvider dataProvider) - { - return "admin"; - } - - private const string AdminPassword = "admin"; - - private static readonly string[] AdminRoles = { "admin", "buildserver", "editor" }; - - public static IRevisionDataProvider GetRevisionDataProvider(string connectionStringName = "MongoDBStorage") - { - const string typeName = "BuildRevisionCounter.Data.MongoDBStorage"; - var type = Type.GetType(typeName); - if (type == null) - throw new ApplicationException("на найден класс для IDataProvider"); - var connectionString = ConfigurationManager.ConnectionStrings[connectionStringName].ConnectionString; - return (IRevisionDataProvider)Activator.CreateInstance(type, connectionString); - } - - public static IUserDataProvider GetUserDataProvider(string connectionStringName = "MongoDBStorage") - { - const string typeName = "BuildRevisionCounter.Data.MongoDBStorage"; - var type = Type.GetType(typeName); - if (type == null) - throw new ApplicationException("на найден класс для IDataProvider"); - var connectionString = ConfigurationManager.ConnectionStrings[connectionStringName].ConnectionString; - return (IUserDataProvider)Activator.CreateInstance(type, connectionString); - } - - public static async Task SetUpAsync(this IDatabaseTestProvider dataProvider) - { - await dataProvider.DropDatabaseAsync(); - await dataProvider.SetUp(); - } - - public static async Task EnsureAdminUser(this IDatabaseTestProvider dataProvider) - { - if (await dataProvider.FindUser(dataProvider.GetAdminName()) == null) - { - await dataProvider.CreateUser(dataProvider.GetAdminName(), AdminPassword, AdminRoles); - } - } - } -} \ No newline at end of file diff --git a/BuildRevisionCounter/Data/MongoDBStorage.cs b/BuildRevisionCounter/Data/MongoDBRevisionStorage.cs similarity index 55% rename from BuildRevisionCounter/Data/MongoDBStorage.cs rename to BuildRevisionCounter/Data/MongoDBRevisionStorage.cs index 7d64a52..eb96a80 100644 --- a/BuildRevisionCounter/Data/MongoDBStorage.cs +++ b/BuildRevisionCounter/Data/MongoDBRevisionStorage.cs @@ -9,35 +9,20 @@ namespace BuildRevisionCounter.Data { - public class MongoDBStorage : IRevisionDataProvider, IUserDataProvider, IDatabaseTestProvider + public class MongoDBRevisionStorage : IRevisionStorage { private readonly IMongoCollection _revisions; - private readonly IMongoCollection _users; - public MongoDBStorage(string connectionString) + public string ConnectionString { get; private set; } + + public MongoDBRevisionStorage(string connectionString) { + ConnectionString = connectionString; + var mongoUrl = MongoUrl.Create(connectionString); var database = new MongoClient(mongoUrl).GetDatabase(mongoUrl.DatabaseName); - - _revisions = database.GetCollection("revisions"); - _users = database.GetCollection("users"); - - Task.Run(() => SetUp()).Wait(); - } - - [MethodUsageScope(MainModuleName = "nunit-agent.exe,nunit.exe,nunit.exe,nunit-x86.exe")] - public async Task SetUp() - { - MethodUsageScopeAttribute.ControlScope(MethodBase.GetCurrentMethod()); - await EnsureUsersIndex(); - await this.EnsureAdminUser(); - } - [MethodUsageScope(MainModuleName = "nunit-agent.exe,nunit.exe,nunit-x86.exe")] - public Task DropDatabaseAsync() - { - MethodUsageScopeAttribute.ControlScope(MethodBase.GetCurrentMethod()); - return _revisions.Database.Client.DropDatabaseAsync(_revisions.Database.DatabaseNamespace.DatabaseName); + _revisions = database.GetCollection("revisions"); } public async Task> GetAllRevision(Int32 pageSize, Int32 pageNumber) @@ -72,15 +57,9 @@ public async Task Bumping(string revisionName) // если не получилось, значит документ еще не был создан // создадим его с начальным значением 0 - try - { - await RevisionInsertAsync(revisionName); + if (await RevisionInsertAsync(revisionName)) return 0; - } - catch (DuplicateKeyException) - { - } - + // если при вставке произошла ошибка значит мы не успели и запись там уже есть // и теперь попытка обновления должна пройти без ошибок result = await FindOneAndUpdateRevisionModelAsync(revisionName); @@ -88,7 +67,7 @@ public async Task Bumping(string revisionName) return result.CurrentNumber; } - private async Task RevisionInsertAsync(string revisionName) + private async Task RevisionInsertAsync(string revisionName) { try { @@ -99,11 +78,12 @@ await _revisions CurrentNumber = 0, Created = DateTime.UtcNow }); + return true; } catch (MongoWriteException ex) { if (ex.WriteError != null && ex.WriteError.Category == ServerErrorCategory.DuplicateKey) - throw new DuplicateKeyException(); + return false; throw; } } @@ -125,46 +105,5 @@ private async Task FindOneAndUpdateRevisionModelAsync(string revi return result; } - - public async Task EnsureUsersIndex() - { - await _users.Indexes.CreateOneAsync( - Builders.IndexKeys.Ascending(u => u.Name), - new CreateIndexOptions { Unique = true, }); - } - - public async Task FindUser(string name) - { - return await _users.Find(u => u.Name == name).SingleOrDefaultAsync(); - } - - /// - /// создать пользователя - /// - /// имя - /// пароль - /// роли - /// - /// В случае дублирования имени пользователя - public async Task CreateUser(string name, string password, string[] roles) - { - try - { - await _users - .InsertOneAsync( - new UserModel - { - Name = name, - Password = password, - Roles = roles - }); - } - catch (MongoWriteException ex) - { - if (ex.WriteError != null && ex.WriteError.Category == ServerErrorCategory.DuplicateKey) - throw new DuplicateKeyException(); - throw; - } - } } } \ No newline at end of file diff --git a/BuildRevisionCounter/Data/MongoDBUserStorage.cs b/BuildRevisionCounter/Data/MongoDBUserStorage.cs new file mode 100644 index 0000000..d5b6767 --- /dev/null +++ b/BuildRevisionCounter/Data/MongoDBUserStorage.cs @@ -0,0 +1,73 @@ +using System.Threading.Tasks; +using BuildRevisionCounter.Interfaces; +using BuildRevisionCounter.Model; +using MongoDB.Driver; + +namespace BuildRevisionCounter.Data +{ + public class MongoDBUserStorage : IUserStorage, IUserDatabaseTestProvider + { + private readonly IMongoCollection _users; + + public string ConnectionString { get; private set; } + + public MongoDBUserStorage(string connectionString) + { + ConnectionString = connectionString; + + var mongoUrl = MongoUrl.Create(connectionString); + var database = new MongoClient(mongoUrl).GetDatabase(mongoUrl.DatabaseName); + + _users = database.GetCollection("users"); + + Task.Run(() => SetUp()).Wait(); + } + + public async Task SetUp() + { + await EnsureUsersIndex(); + await this.EnsureAdminUser(); + } + + private async Task EnsureUsersIndex() + { + await _users.Indexes.CreateOneAsync( + Builders.IndexKeys.Ascending(u => u.Name), + new CreateIndexOptions {Unique = true,}); + } + + public async Task FindUser(string name) + { + return await _users.Find(u => u.Name == name).SingleOrDefaultAsync(); + } + + /// + /// создать пользователя + /// + /// имя + /// пароль + /// роли + /// + /// В случае дублирования имени пользователя + public async Task CreateUser(string name, string password, string[] roles) + { + try + { + await _users + .InsertOneAsync( + new UserModel + { + Name = name, + Password = password, + Roles = roles + }); + } + catch (MongoWriteException ex) + { + if (ex.WriteError != null && ex.WriteError.Category == ServerErrorCategory.DuplicateKey) + throw new DuplicateKeyException(); + throw; + } + } + } +} \ No newline at end of file diff --git a/BuildRevisionCounter/Interfaces/IRevisionDataProvider.cs b/BuildRevisionCounter/Interfaces/IRevisionStorage.cs similarity index 92% rename from BuildRevisionCounter/Interfaces/IRevisionDataProvider.cs rename to BuildRevisionCounter/Interfaces/IRevisionStorage.cs index 8c54395..fd27f5f 100644 --- a/BuildRevisionCounter/Interfaces/IRevisionDataProvider.cs +++ b/BuildRevisionCounter/Interfaces/IRevisionStorage.cs @@ -8,7 +8,7 @@ namespace BuildRevisionCounter.Interfaces /// /// Интерфейс для получения данных из БД. /// - public interface IRevisionDataProvider + public interface IRevisionStorage { Task> GetAllRevision(Int32 pageSize, Int32 pageNumber); diff --git a/BuildRevisionCounter/Interfaces/IDatabaseTestProvider.cs b/BuildRevisionCounter/Interfaces/IUserDatabaseTestProvider.cs similarity index 79% rename from BuildRevisionCounter/Interfaces/IDatabaseTestProvider.cs rename to BuildRevisionCounter/Interfaces/IUserDatabaseTestProvider.cs index b889297..276623f 100644 --- a/BuildRevisionCounter/Interfaces/IDatabaseTestProvider.cs +++ b/BuildRevisionCounter/Interfaces/IUserDatabaseTestProvider.cs @@ -6,14 +6,14 @@ namespace BuildRevisionCounter.Interfaces /// /// Интерфейс для проверки операций с БД. /// - public interface IDatabaseTestProvider + public interface IUserDatabaseTestProvider { + string ConnectionString { get; } + Task SetUp(); - - Task DropDatabaseAsync(); - + Task FindUser(string name); - + Task CreateUser(string name, string password, string[] roles); } } \ No newline at end of file diff --git a/BuildRevisionCounter/Interfaces/IUserDataProvider.cs b/BuildRevisionCounter/Interfaces/IUserStorage.cs similarity index 86% rename from BuildRevisionCounter/Interfaces/IUserDataProvider.cs rename to BuildRevisionCounter/Interfaces/IUserStorage.cs index 08f8417..d8384ef 100644 --- a/BuildRevisionCounter/Interfaces/IUserDataProvider.cs +++ b/BuildRevisionCounter/Interfaces/IUserStorage.cs @@ -3,7 +3,7 @@ namespace BuildRevisionCounter.Interfaces { - public interface IUserDataProvider + public interface IUserStorage { Task FindUser(string name); diff --git a/BuildRevisionCounter/Security/BasicAuthenticationFilter.cs b/BuildRevisionCounter/Security/BasicAuthenticationFilter.cs index 0ba43ba..b25af5e 100644 --- a/BuildRevisionCounter/Security/BasicAuthenticationFilter.cs +++ b/BuildRevisionCounter/Security/BasicAuthenticationFilter.cs @@ -27,18 +27,18 @@ public class BasicAuthenticationFilter : IAuthenticationFilter EncoderFallback.ExceptionFallback, DecoderFallback.ExceptionFallback); - private readonly IUserDataProvider _dataProvider; + private readonly IUserStorage _dataStorage; /// /// Конструктор фильтра. /// - /// Объект для получения данных из БД. - public BasicAuthenticationFilter(IUserDataProvider dataProvider) + /// Объект для получения данных из БД. + public BasicAuthenticationFilter(IUserStorage dataStorage) { - if (dataProvider == null) - throw new ArgumentNullException("dataProvider"); + if (dataStorage == null) + throw new ArgumentNullException("dataStorage"); - _dataProvider = dataProvider; + _dataStorage = dataStorage; } public string Realm { get; set; } @@ -87,7 +87,7 @@ public Task ChallengeAsync(HttpAuthenticationChallengeContext context, Cancellat private async Task Authenticate(string userName, string password) { IPrincipal principal = null; - var user = await _dataProvider.FindUser(userName); + var user = await _dataStorage.FindUser(userName); if (user != null && user.Password == password) { principal = new GenericPrincipal(new GenericIdentity(userName), user.Roles); diff --git a/BuildRevisionCounter/Security/MethodUsageScopeAttribute.cs b/BuildRevisionCounter/Security/MethodUsageScopeAttribute.cs deleted file mode 100644 index b55faf3..0000000 --- a/BuildRevisionCounter/Security/MethodUsageScopeAttribute.cs +++ /dev/null @@ -1,25 +0,0 @@ -using System; -using System.Reflection; - -namespace BuildRevisionCounter.Security -{ - public class MethodUsageScopeAttribute: Attribute - { - public string MainModuleName { get; set; } - - public static void ControlScope(MemberInfo methodBase) - { - var att = (MethodUsageScopeAttribute)GetCustomAttribute(methodBase, typeof(MethodUsageScopeAttribute)); - if (att == null || att.MainModuleName == null) - return; - var allowNames = att.MainModuleName.ToLower().Split(','); - var moduleName = System.Diagnostics.Process.GetCurrentProcess().MainModule.ModuleName.ToLower(); - foreach (var name in allowNames) - { - if (name == moduleName) - return; - } - throw new InvalidOperationException(); - } - } -} \ No newline at end of file diff --git a/BuildRevisionCounter/Startup.cs b/BuildRevisionCounter/Startup.cs index a732cfa..0b7acfd 100644 --- a/BuildRevisionCounter/Startup.cs +++ b/BuildRevisionCounter/Startup.cs @@ -57,8 +57,8 @@ private static IKernel CreateKernel() /// Ядро Ninject. private static void RegisterServices(IKernel kernel) { - kernel.Bind().ToMethod(c => DbProviderUtil.GetRevisionDataProvider()).InSingletonScope(); - kernel.Bind().ToMethod(c => DbProviderUtil.GetUserDataProvider()).InSingletonScope(); + kernel.Bind().ToMethod(c => DBStorageUtil.GetRevisionStorage()).InSingletonScope(); + kernel.Bind().ToMethod(c => DBStorageUtil.GetUserStorage()).InSingletonScope(); kernel.BindHttpFilter(FilterScope.Controller).WhenControllerHas(); } }