ASP.NET MVCに関する注意事項。 パート1(そして唯一のもの)

最近、ASP.NET MVCに関する記事が頻繁にHabréに掲載され始めました。 ただし、この記事では、上記のフレームワークでのアプリケーションの構築に関するいくつかのメモをしたいと思います。NuGetパッケージの最小セット(それなしでは作業を開始するのは罪です)、ログ、標準メンバーシップ、プロファイルプロバイダーを使用する場合の落とし穴。 そして最後に、MVC 4のWeb APIが私たち全員が待ち望んでいた理由です。



Nugetパッケージ



したがって、ASP.NET MVCでWebアプリケーションの開発を開始できないパッケージがないと判断しましょう。 以下のリストには、ソリューションの作成時にデフォルトでインストールされる[パッケージ]が含まれていますが、それらは引き続き含まれます。



Entity Framework 4.1-質問は、なぜですか? さて、例で説明しましょう。 他の同様のORMフレームワークなど、十分な数のORMフレームワークがあります(NHibernateには1つの価値があります)。 ほんの数年前、軽量(比較的、合成テストで判断)LINQ to SQLを使用することから始めることをお勧めします。 しかし! Entity Framework 4.1のリリースとCode Firstの組み合わせは、すべての欠点を上回りました。アプリケーションデータレイヤーのプロトタイピングは喜びでした。 最初にデザイナーで作業する必要がある場合、DBMLファイルを扱う場合、ここではPOCOのみを使用します。 たとえば、ストアのデータモデル:



public class Product { public int ProductId { get; set; } public string Name { get; set; } public int CategoryId { get; set; } public virtual Category Category { get; set; } public int Price { get; set; } public DateTime CreationDate { get; set; } public string Description { get; set; } } public class Category { public int CategoryId { get; set; } public string Name { get; set; } public virtual ICollection<Product> Products { get; set; } } public class ProductsContext : DbContext { public DbSet<Category> Categories { get; set; } }
      
      





MvcScaffolding -CRUDパネルをすばやくスケッチする必要がありますか? 既にEFモデル、またはLINQ to SQLをお持ちですか? 次に、NuGetウィンドウに次のコマンドを入力し、コード生成を楽しんでください。

Scaffold Controller [ ] –Repository





–Repositoryフラグを使用すると、データレイヤーを操作するためのリポジトリを作成することもできます。

たとえば、上記のモデルを使用します。

入った後

Scaffold Controller Product –Repository





次のCRUDページと抽象リポジトリが生成されます。



 public interface IProductRepository { IQueryable<Product> All { get; } IQueryable<Product> AllIncluding(params Expression<Func<Product, object>>[] includeProperties); Product Find(int id); void InsertOrUpdate(Product product); void Delete(int id); void Save(); }
      
      





また、その実装:



 public class ProductRepository : IProductRepository { ProductsContext context = new ProductsContext(); public IQueryable<Product> All { get { return context.Products; } } public IQueryable<Product> AllIncluding(params Expression<Func<Product, object>>[] includeProperties) { IQueryable<Product> query = context.Products; foreach (var includeProperty in includeProperties) { query = query.Include(includeProperty); } return query; } public Product Find(int id) { return context.Products.Find(id); } public void InsertOrUpdate(Product product) { if (product.ProductId == default(int)) { // New entity context.Products.Add(product); } else { // Existing entity context.Entry(product).State = EntityState.Modified; } } public void Delete(int id) { var product = context.Products.Find(id); context.Products.Remove(product); } public void Save() { context.SaveChanges(); } }
      
      







より詳細なレビューについては、クリエイター自身からの一連の記事を読むことをお勧めします。



Ninject-私は個人的に抽象化なしで働く機会はありません。 ASP.NET MVCには、工場の機能を制御/拡張するための多くの機能があります。 したがって、特定のクラス実装で機能を結び付けるのは悪い形です。 なぜNinjectなのか? 答えは簡単です。軽量で、多くの拡張機能があり、積極的に開発されています。

それをインストールし、 MVC3を追加します。

その後、App_Startフォルダーが表示され、NinjectMVC3.csファイルが配置されます。

DIを実装するには、モジュールを作成します。



 class RepoModule : NinjectModule { public override void Load() { Bind<ICategoryRepository>().To<CategoryRepository>(); Bind<IProductRepository>().To<ProductRepository>(); } }
      
      







CreateKernelメソッドのNinjectMVC3.csファイルで、次のように記述します。



 var modules = new INinjectModule[] { new RepoModule() }; var kernel = new StandardKernel(modules); RegisterServices(kernel); return kernel;
      
      







次に、コントローラーを記述します。



 public class ProductsController : Controller { private readonly IProductRepository productRepository; public ProductsController(IProductRepository productRepository) { this.productRepository = productRepository; } }
      
      





NLog-アプリケーションの動作方法、操作を実行する際の成功/失敗の見つけ方 最も簡単な解決策は、ロギングを使用することです。 バイクを書くのは意味がありません。 とりわけ、NLogとlog4netを区別できると思います。 後者はJava(log4j)を使用した直接ポートです。 しかし、その開発はまったく放棄されていないとしても、あまり活発ではありません。 それどころか、NLogは活発に開発されており、豊富な機能とシンプルなAPIを備えています。

ロガーをすばやく追加する方法:



 public class ProductController : Controller { private static Logger log = LogManager.GetCurrentClassLogger(); public ActionResult DoStuff() { //very important stuff log.Info("Everything is OK!"); return View(); } }
      
      





PagedList-ページめくりアルゴリズムが必要ですか? はい、自分で座って思いつくことができます。 しかし、なぜですか? この記事には、それを使った作業の詳細な説明があります。



Lucene.NET-データベース自体の検索を使用してまだ消去していますか? 忘れて! 数分で、超高速検索ができます。

それとSimpleLuceneアドオンをインストールします。

まず、インデックスの作成作業を自動化します。



 public class ProductIndexDefinition : IIndexDefinition<Product> { public Document Convert(Product entity) { var document = new Document(); document.Add(new Field("ProductId", entity.ProductId.ToString(), Field.Store.YES, Field.Index.NOT_ANALYZED)); document.Add(new Field("Name", entity.Name, Field.Store.YES, Field.Index.ANALYZED)); if (!string.IsNullOrEmpty(entity.Description)) { document.Add(new Field("Description", entity.Description, Field.Store.YES, Field.Index.ANALYZED)); } document.Add(new Field("CreationDate", DateTools.DateToString(entity.CreationDate, DateTools.Resolution.DAY), Field.Store.YES, Field.Index.NOT_ANALYZED)); if (entity.Price != null) { var priceField = new NumericField("Price", Field.Store.YES, true); priceField.SetIntValue(entity.Price); document.Add(priceField); } document.Add(new Field("CategoryId", entity.CategoryId.ToString(), Field.Store.YES, Field.Index.NOT_ANALYZED)); return document; } public Term GetIndex(Product entity) { return new Term("ProductId", entity.ProductId.ToString()); } }
      
      







Convertメソッドで確認できるように、Lucene DocumentでPOCOをシリアル化します。

コントローラーコード:



 public ActionResult Create(Product product) { if (ModelState.IsValid) { product.CreationDate = DateTime.Now; productRepository.InsertOrUpdate(product); productRepository.Save(); // index location var indexLocation = new FileSystemIndexLocation(new DirectoryInfo(Server.MapPath("~/Index"))); var definition = new ProductIndexDefinition(); var task = new EntityUpdateTask<Product>(product, definition, indexLocation); task.IndexOptions.RecreateIndex = false; task.IndexOptions.OptimizeIndex = true; //IndexQueue.Instance.Queue(task); var indexWriter = new DirectoryIndexWriter(new DirectoryInfo(Server.MapPath("~/Index")), false); using (var indexService = new IndexService(indexWriter)) { task.Execute(indexService); } return RedirectToAction("Index"); } else { ViewBag.PossibleCategories = categoryRepository.All; return View(); } }
      
      







結果を表示するには、ResultDefinitionを作成します。



 public class ProductResultDefinition : IResultDefinition<Product> { public Product Convert(Document document) { var product = new Product(); product.ProductId = document.GetValue<int>("ProductId"); product.Name = document.GetValue("Name"); product.Price = document.GetValue<int>("Price"); product.CategoryId = document.GetValue<int>("CategoryId"); product.CreationDate = DateTools.StringToDate(document.GetValue("CreationDate")); product.Description = document.GetValue("Description"); return product; } }
      
      







これは、POCOが逆シリアル化する場所です。

最後に、リクエストを使用して作業を自動化します。



 public class ProductQuery : QueryBase { public ProductQuery(Query query) : base(query) { } public ProductQuery() { } public ProductQuery WithKeywords(string keywords) { if (!string.IsNullOrEmpty(keywords)) { string[] fields = { "Name", "Description" }; var parser = new MultiFieldQueryParser(Lucene.Net.Util.Version.LUCENE_29, fields, new StandardAnalyzer(Lucene.Net.Util.Version.LUCENE_29)); Query multiQuery = parser.Parse(keywords); this.AddQuery(multiQuery); } return this; } } }
      
      







それでは、コントローラーに移りましょう。



 public ActionResult Search(string searchText, bool? orderByDate) { string IndexPath = Server.MapPath("~/Index"); var indexSearcher = new DirectoryIndexSearcher(new DirectoryInfo(IndexPath), true); using (var searchService = new SearchService(indexSearcher)) { var query = new ProductQuery().WithKeywords(searchText); var result = searchService.SearchIndex<Product>(query.Query, new ProductResultDefinition()); if (orderByDate.HasValue) { return View(result.Results.OrderBy(x => x.CreationDate).ToList()) } return View(result.Results.ToList()); } }
      
      







JSのリアクティブエクステンション -クライアントの基礎である必要があります。 いいえ、正直に言って、単体テストの可能性を備えた、クライアント上でのアプリケーションフレームワークのよりスムーズな作成を引き続き求める必要があります。 Rx開発に関する私の記事を読むことをお勧めします。



認証と承認



すぐに警告します-標準のAspNetMembershipProviderを使用しないでください! 箱から出して彼の巨大なストアドプロシージャを見ると、ただ彼を捨てたいだけです。

C:\ Windows \ Microsoft.NET \ Framework \ v4.0.30319 \フォルダーのInstallMembership.sqlおよびInstallProfile.SQLファイルを開きます。

たとえば、InstallMembership.sqlのFindUsersByNameのSQLコードは次のようになります。



 CREATE PROCEDURE dbo.aspnet_Membership_FindUsersByName @ApplicationName nvarchar(256), @UserNameToMatch nvarchar(256), @PageIndex int, @PageSize int AS BEGIN DECLARE @ApplicationId uniqueidentifier SELECT @ApplicationId = NULL SELECT @ApplicationId = ApplicationId FROM dbo.aspnet_Applications WHERE LOWER(@ApplicationName) = LoweredApplicationName IF (@ApplicationId IS NULL) RETURN 0 -- Set the page bounds DECLARE @PageLowerBound int DECLARE @PageUpperBound int DECLARE @TotalRecords int SET @PageLowerBound = @PageSize * @PageIndex SET @PageUpperBound = @PageSize - 1 + @PageLowerBound -- Create a temp table TO store the select results CREATE TABLE #PageIndexForUsers ( IndexId int IDENTITY (0, 1) NOT NULL, UserId uniqueidentifier ) -- Insert into our temp table INSERT INTO #PageIndexForUsers (UserId) SELECT u.UserId FROM dbo.aspnet_Users u, dbo.aspnet_Membership m WHERE u.ApplicationId = @ApplicationId AND m.UserId = u.UserId AND u.LoweredUserName LIKE LOWER(@UserNameToMatch) ORDER BY u.UserName SELECT u.UserName, m.Email, m.PasswordQuestion, m.Comment, m.IsApproved, m.CreateDate, m.LastLoginDate, u.LastActivityDate, m.LastPasswordChangedDate, u.UserId, m.IsLockedOut, m.LastLockoutDate FROM dbo.aspnet_Membership m, dbo.aspnet_Users u, #PageIndexForUsers p WHERE u.UserId = p.UserId AND u.UserId = m.UserId AND p.IndexId >= @PageLowerBound AND p.IndexId <= @PageUpperBound ORDER BY u.UserName SELECT @TotalRecords = COUNT(*) FROM #PageIndexForUsers RETURN @TotalRecords END
      
      







そして、InstallProfile.SQLのProfile_GetProfilesは次のとおりです。



 CREATE PROCEDURE dbo.aspnet_Profile_GetProfiles @ApplicationName nvarchar(256), @ProfileAuthOptions int, @PageIndex int, @PageSize int, @UserNameToMatch nvarchar(256) = NULL, @InactiveSinceDate datetime = NULL AS BEGIN DECLARE @ApplicationId uniqueidentifier SELECT @ApplicationId = NULL SELECT @ApplicationId = ApplicationId FROM aspnet_Applications WHERE LOWER(@ApplicationName) = LoweredApplicationName IF (@ApplicationId IS NULL) RETURN -- Set the page bounds DECLARE @PageLowerBound int DECLARE @PageUpperBound int DECLARE @TotalRecords int SET @PageLowerBound = @PageSize * @PageIndex SET @PageUpperBound = @PageSize - 1 + @PageLowerBound -- Create a temp table TO store the select results CREATE TABLE #PageIndexForUsers ( IndexId int IDENTITY (0, 1) NOT NULL, UserId uniqueidentifier ) -- Insert into our temp table INSERT INTO #PageIndexForUsers (UserId) SELECT u.UserId FROM dbo.aspnet_Users u, dbo.aspnet_Profile p WHERE ApplicationId = @ApplicationId AND u.UserId = p.UserId AND (@InactiveSinceDate IS NULL OR LastActivityDate <= @InactiveSinceDate) AND ( (@ProfileAuthOptions = 2) OR (@ProfileAuthOptions = 0 AND IsAnonymous = 1) OR (@ProfileAuthOptions = 1 AND IsAnonymous = 0) ) AND (@UserNameToMatch IS NULL OR LoweredUserName LIKE LOWER(@UserNameToMatch)) ORDER BY UserName SELECT u.UserName, u.IsAnonymous, u.LastActivityDate, p.LastUpdatedDate, DATALENGTH(p.PropertyNames) + DATALENGTH(p.PropertyValuesString) + DATALENGTH(p.PropertyValuesBinary) FROM dbo.aspnet_Users u, dbo.aspnet_Profile p, #PageIndexForUsers i WHERE u.UserId = p.UserId AND p.UserId = i.UserId AND i.IndexId >= @PageLowerBound AND i.IndexId <= @PageUpperBound SELECT COUNT(*) FROM #PageIndexForUsers DROP TABLE #PageIndexForUsers END
      
      







ご覧のとおり、一時テーブルは常に作成されており、ハードウェアが無効になります。 1秒あたり100のそのような呼び出しがあると想像してください。

したがって、常に独自のプロバイダーを作成してください。



ASP.NET MVC4 Web API



ASP.NET MVCは、RESTfulアプリケーションを作成するための優れたフレームワークです。 APIを提供するには、たとえば、次のコードを記述できます。



 public class AjaxProductsController : Controller { private readonly IProductRepository productRepository; public AjaxProductsController(IProductRepository productRepository) { this.productRepository = productRepository; } public ActionResult Details(int id) { return Json(productRepository.Find(id)); } public ActionResult List(int category) { var products = from p in productRepository.All where p.CategoryId == category select p; return Json(products.ToList()); } }
      
      







はい、1つの方法は、AJAXリクエストに対応するために別のコントローラーを書くことでした。

もう1つはスパゲッティコードです。



 public class ProductsController : Controller { private readonly IProductRepository productRepository; public ProductsController(IProductRepository productRepository) { this.productRepository = productRepository; } public ActionResult List(int category) { var products = from p in productRepository.All where p.CategoryId == category select p; if (Request.IsAjaxRequest()) { return Json(products.ToList()); } return View(products.ToList()); } }
      
      







また、CRUD操作を追加する必要がある場合は、次のようにします。



 [HttpPost] public ActionResult Create(Product product) { if (ModelState.IsValid) { productRepository.InsertOrUpdate(product); productRepository.Save(); return RedirectToAction("Index"); } return View(); }
      
      





属性を見るとわかるように、コード内のAJAX検出は最もクリーンなコードではありません。 APIを書いていますよね?

MVC4のリリースは、Web APIの新しい機能をマークしました。 一見すると、これはMVCコントローラーとWCF Data Servicesの混合物です。

Web APIトピックに関するチュートリアルは提供しませんが、 ASP.NET MVCサイト自体には多数あります。

上記の書き直されたコードの例のみを示します。

最初に、ProductRepositoryからInsertOrUpdateメソッドを変更しましょう。



 public Product InsertOrUpdate(Product product) { if (product.ProductId == default(int)) { // New entity return context.Products.Add(product); } // Existing entity context.Entry(product).State = EntityState.Modified; return context.Entry(product).Entity; }
      
      







そして、コントローラー自体を作成します。



 public class ProductsController : ApiController { /* *  */ public IEnumerable<Product> GetAllProducts(int category) { var products = from p in productRepository.All where p.CategoryId == category select p; return products.ToList(); } // Not the final implementation! public Product PostProduct(Product product) { var entity = productRepository.InsertOrUpdate(product); return entity; } }
      
      







それで、いくつかのポイント、何が変更され、どのように機能するか:



私は、Web APIがMVCとWCF Data Servicesの混合物であることをもう少し指摘しました。 しかし、これはどこで表現されていますか? 簡単です-新しいAPIはODataをサポートします! そして、同様の原理で機能します。

たとえば、並べ替えを示すには、メソッド自体にパラメーターを指定する必要がありました。



 public ActionResult List(string sortOrder, int category) { var products = from p in productRepository.All where p.CategoryId == category select p; switch (sortOrder.ToLower()) { case "name": products = products.OrderBy(x => x.Name); break; case "desc": products = products.OrderBy(x => x.Description); break; } return Json(products.ToList()); }
      
      







次に、GetAllProductsメソッドを変更するだけです。



 public IQueryable<Product> GetAllProducts(int category) { var products = from p in productRepository.All where p.CategoryId == category select p; return products; }
      
      







また、たとえばブラウザで次のように入力します:

http://localhost/api/products?category=1&$orderby=Name







したがって、注意散漫を取り除き、API自体の作成に集中できるようになりました。



ご清聴ありがとうございました!




All Articles