首页 > 其他分享 >MVC的最佳架构

MVC的最佳架构

时间:2023-08-20 09:00:19浏览次数:32  
标签:架构 Name get void class 最佳 MVC new public

原文地址

https://chsakell.com/2015/02/15/asp-net-mvc-solution-architecture-best-practices/

  

  为Web应用程序选择正确的架构是必须的,尤其是对于大型应用程序。使用默认的Visual Studio ASP。NET MVC Web应用程序项目模板,添加带有脚手架选项的控制器,只需引导应用程序并在几分钟内创建页面和数据,听起来确实很棒,但老实说,这并不总是正确的选择。查看所有默认选项,将业务、数据和表示逻辑保持在同一个项目中,将影响解决方案中的几个因素,如可伸缩性、可用性或可测试性。在这篇文章中,我们将看到如何保持事物的整洁,创建一个高度松散耦合的ASP。NET MVC解决方案,其中数据访问、业务和表示层以正确的方式定义。为此,我们将使用几种模式和框架,其中一些将在下面介绍。

 

实体框架代码优先开发

通用存储库模式

使用Autofac框架的依赖注入

自动映射器

让我们开始吧。假设我们想构建一个名为“Store”的电子商店Web应用程序,请创建一个同名的空白解决方案。

 

模型

将类库项目添加到解决方案中,名为Store.Model。这个库是我们保存所有域对象的地方。实体框架将依靠它们来构建数据库,但我们不会在此项目上使用DataAnnotations属性配置CodeFirst。相反,我们将使用Fluent API将所有代码优先配置放在特定的配置类中。添加一个名为Models的文件夹,并添加以下两个简单类。

Gadget.cs
public class Gadget
{
public int GadgetID { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public decimal Price { get; set; }
public string Image { get; set; }

public int CategoryID { get; set; }
public Category Category { get; set; }
}

Category.cs
public class Category
{
public int CategoryID { get; set; }
public string Name { get; set; }
public DateTime? DateCreated { get; set; }
public DateTime? DateUpdated { get; set; }

public virtual List<Gadget> Gadgets { get; set; }

public Category()
{
DateCreated = DateTime.Now;
}
}

Data Access Layer and Repositories
数据访问层和存储库
该层的目的是直接访问数据库。它是唯一负责与数据库通信的层。如果其他层想要访问数据库,那么这将通过我们将在本项目中定义的一些类(存储库)来完成。这将是添加名为Store的新类库项目的唯一方法。数据,并确保添加对先前创建的项目Store.Model的引用。使用Nuget包管理器安装实体框架。我们要做的第一件事是为我们的域对象定义实体类型配置。添加名为Configuration的文件夹,其中包含以下两个继承EntityTypeConfiguration类的类。

GadgetConfiguration.cs
public class GadgetConfiguration: EntityTypeConfiguration<Gadget>
{
public GadgetConfiguration()
{
ToTable("Gadgets");
Property(g => g.Name).IsRequired().HasMaxLength(50);
Property(g => g.Price).IsRequired().HasPrecision(8, 2);
Property(g => g.CategoryID).IsRequired();
}
}

CategoryConfiguration.cs
public class CategoryConfiguration : EntityTypeConfiguration<Category>
{
public CategoryConfiguration()
{
ToTable("Categories");
Property(c => c.Name).IsRequired().HasMaxLength(50);
}
}

没有任何困难的配置需要解释,关键是要理解在哪里放置正确的对象。接下来我们要做的是创建DbContext类,该类将负责访问数据库。在当前项目的根下添加以下类。
StoreEntities.cs
lic class StoreEntities : DbContext
{
public StoreEntities() : base("StoreEntities") { }

public DbSet<Gadget> Gadgets { get; set; }
public DbSet<Category> Categories { get; set; }

public virtual void Commit()
{
base.SaveChanges();
}

protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Configurations.Add(new GadgetConfiguration());
modelBuilder.Configurations.Add(new CategoryConfiguration());
}
}
我们希望在应用程序第一次启动时为数据库添加种子,因此也将以下类添加到项目的根目录中。
StoreSeedData
public class StoreSeedData : DropCreateDatabaseIfModelChanges<StoreEntities>
{
protected override void Seed(StoreEntities context)
{
GetCategories().ForEach(c => context.Categories.Add(c));
GetGadgets().ForEach(g => context.Gadgets.Add(g));

context.Commit();
}

private static List<Category> GetCategories()
{
return new List<Category>
{
new Category {
Name = "Tablets"
},
new Category {
Name = "Laptops"
},
new Category {
Name = "Mobiles"
}
};
}

private static List<Gadget> GetGadgets()
{
return new List<Gadget>
{
new Gadget {
Name = "ProntoTec 7",
Description = "Android 4.4 KitKat Tablet PC, Cortex A8 1.2 GHz Dual Core Processor,512MB / 4GB,Dual Camera,G-Sensor (Black)",
CategoryID = 1,
Price = 46.99m,
Image = "prontotec.jpg"
},
new Gadget {
Name = "Samsung Galaxy",
Description = "Android 4.4 Kit Kat OS, 1.2 GHz quad-core processor",
CategoryID = 1,
Price = 120.95m,
Image= "samsung-galaxy.jpg"
},
new Gadget {
Name = "NeuTab® N7 Pro 7",
Description = "NeuTab N7 Pro tablet features the amazing powerful, Quad Core processor performs approximately Double multitasking running speed, and is more reliable than ever",
CategoryID = 1,
Price = 59.99m,
Image= "neutab.jpg"
},
new Gadget {
Name = "Dragon Touch® Y88X 7",
Description = "Dragon Touch Y88X tablet featuring the incredible powerful Allwinner Quad Core A33, up to four times faster CPU, ensures faster multitasking speed than ever. With the super-portable size, you get a robust power in a device that can be taken everywhere",
CategoryID = 1,
Price = 54.99m,
Image= "dragon-touch.jpg"
},
new Gadget {
Name = "Alldaymall A88X 7",
Description = "This Alldaymall tablet featuring the incredible powerful Allwinner Quad Core A33, up to four times faster CPU, ensures faster multitasking speed than ever. With the super-portable size, you get a robust power in a device that can be taken everywhere",
CategoryID = 1,
Price = 47.99m,
Image= "Alldaymall.jpg"
},
new Gadget {
Name = "ASUS MeMO",
Description = "Pad 7 ME170CX-A1-BK 7-Inch 16GB Tablet. Dual-Core Intel Atom Z2520 1.2GHz CPU",
CategoryID = 1,
Price = 94.99m,
Image= "asus-memo.jpg"
},
// Code ommitted
};
}
}
您可以看到,这个接口继承了IDisposable接口,因此将实现IDbFactory接口的Concrete类也必须实现IDisposble接口。要以干净的方式做到这一点,请添加一个将实现IDisposable接口的Disposable类。那么任何将实现IDbFactory接口的类都只想继承这个类。
Disposable.cs
public class Disposable : IDisposable
{
private bool isDisposed;

~Disposable()
{
Dispose(false);
}

public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
private void Dispose(bool disposing)
{
if (!isDisposed && disposing)
{
DisposeCore();
}

isDisposed = true;
}

// Ovveride this to dispose custom objects
protected virtual void DisposeCore()
{
}
}

我已经强调了DisposeCore虚拟方法,因为这个方法将使其他类继承这个方法,以按需要的方式处理自己的对象。现在添加IDbFactory接口的实现类。

DbFactory.cs
public class DbFactory : Disposable, IDbFactory
{
StoreEntities dbContext;

public StoreEntities Init()
{
return dbContext ?? (dbContext = new StoreEntities());
}

protected override void DisposeCore()
{
if (dbContext != null)
dbContext.Dispose();
}
}
现在是时候创建一个通用的IRepository接口了,我们将在这里声明存储库将支持的默认操作。我在这里添加了一些我认为最常用的操作,但您可以根据需要扩展这些操作。
IRepository.cs
public interface IRepository<T> where T : class
{
// Marks an entity as new
void Add(T entity);
// Marks an entity as modified
void Update(T entity);
// Marks an entity to be removed
void Delete(T entity);
void Delete(Expression<Func<T, bool>> where);
// Get an entity by int id
T GetById(int id);
// Get an entity using delegate
T Get(Expression<Func<T, bool>> where);
// Gets all entities of type T
IEnumerable<T> GetAll();
// Gets entities using delegate
IEnumerable<T> GetMany(Expression<Func<T, bool>> where);
}


请注意,CRUD操作被注释为Mark to do something…这意味着当存储库实现添加、更新或删除实体时,此时不会向数据库发送命令。相反,调用者(服务层)将负责通过IUnitOfWork注入的实例向数据库发送Commit命令。为此,将使用名为UnitOfWork的模式。将以下两个文件添加到Infrastructure文件夹中

IUnitOfWork.cs
public interface IUnitOfWork
{
void Commit();
}

UnitOfWork.cs
public class UnitOfWork : IUnitOfWork
{
private readonly IDbFactory dbFactory;
private StoreEntities dbContext;

public UnitOfWork(IDbFactory dbFactory)
{
this.dbFactory = dbFactory;
}

public StoreEntities DbContext
{
get { return dbContext ?? (dbContext = dbFactory.Init()); }
}

public void Commit()
{
DbContext.Commit();
}
}
与我们使用Disposable类的方式相同,我们将使用一个抽象类,它具有IRepository接口的虚拟实现。这个基类将从所有特定的存储库继承,因此将实现IRepository接口。添加以下类。
RepositoryBase.cs
public abstract class RepositoryBase<T> where T : class
{
#region Properties
private StoreEntities dataContext;
private readonly IDbSet<T> dbSet;

protected IDbFactory DbFactory
{
get;
private set;
}

protected StoreEntities DbContext
{
get { return dataContext ?? (dataContext = DbFactory.Init()); }
}
#endregion

protected RepositoryBase(IDbFactory dbFactory)
{
DbFactory = dbFactory;
dbSet = DbContext.Set<T>();
}

#region Implementation
public virtual void Add(T entity)
{
dbSet.Add(entity);
}

public virtual void Update(T entity)
{
dbSet.Attach(entity);
dataContext.Entry(entity).State = EntityState.Modified;
}

public virtual void Delete(T entity)
{
dbSet.Remove(entity);
}

public virtual void Delete(Expression<Func<T, bool>> where)
{
IEnumerable<T> objects = dbSet.Where<T>(where).AsEnumerable();
foreach (T obj in objects)
dbSet.Remove(obj);
}

public virtual T GetById(int id)
{
return dbSet.Find(id);
}

public virtual IEnumerable<T> GetAll()
{
return dbSet.ToList();
}

public virtual IEnumerable<T> GetMany(Expression<Func<T, bool>> where)
{
return dbSet.Where(where).ToList();
}

public T Get(Expression<Func<T, bool>> where)
{
return dbSet.Where(where).FirstOrDefault<T>();
}

#endregion

}

由于实现被标记为虚拟,因此任何存储库都可以根据需要对特定操作进行验证。现在混凝土存储库:添加一个名为repositories的新文件夹,并添加以下两个类:
GadgetRepository.cs
public class GadgetRepository : RepositoryBase<Gadget>, IGadgetRepository
{
public GadgetRepository(IDbFactory dbFactory)
: base(dbFactory) { }
}

public interface IGadgetRepository : IRepository<Gadget>
{

}

 

CategoryRepository.cs
public class CategoryRepository : RepositoryBase<Category>, ICategoryRepository
{
public CategoryRepository(IDbFactory dbFactory)
: base(dbFactory) { }

public Category GetCategoryByName(string categoryName)
{
var category = this.DbContext.Categories.Where(c => c.Name == categoryName).FirstOrDefault();

return category;
}

public override void Update(Category entity)
{
entity.DateUpdated = DateTime.Now;
base.Update(entity);
}
}
public interface ICategoryRepository : IRepository<Category>
{
Category GetCategoryByName(string categoryName);
}

您可以看到,GadgetRepository支持使用默认行为的默认操作,当然这是可以的。另一方面,您可以看到一个示例,其中特定的存储库需要扩展其操作(GetCategoryByName)或覆盖默认操作(Update)。通常,您为每个Model类添加一个存储库,因此每个类型为T的存储库都负责通过DbContext.Set<T>操作特定的DbSet。我们已经完成了数据访问层的实现,因此可以继续下一个。

 

服务层
您希望公开MVC控制器的哪些操作?业务逻辑将在哪里实现?是的,你猜对了,就在这一层。添加名为Store.Service的新类库项目。您必须添加对先前创建的两个项目Store的引用。模型和存储数据。请注意,我还没有告诉您在这个项目中安装Entity Framework。我不会这样做,因为所需的任何数据库操作都将通过我们之前创建的注入存储库完成。将第一个服务文件添加到此项目。
GadgetService.cs
// operations you want to expose
public interface IGadgetService
{
IEnumerable<Gadget> GetGadgets();
IEnumerable<Gadget> GetCategoryGadgets(string categoryName, string gadgetName = null);
Gadget GetGadget(int id);
void CreateGadget(Gadget gadget);
void SaveGadget();
}

public class GadgetService : IGadgetService
{
private readonly IGadgetRepository gadgetsRepository;
private readonly ICategoryRepository categoryRepository;
private readonly IUnitOfWork unitOfWork;

public GadgetService(IGadgetRepository gadgetsRepository, ICategoryRepository categoryRepository, IUnitOfWork unitOfWork)
{
this.gadgetsRepository = gadgetsRepository;
this.categoryRepository = categoryRepository;
this.unitOfWork = unitOfWork;
}

#region IGadgetService Members

public IEnumerable<Gadget> GetGadgets()
{
var gadgets = gadgetsRepository.GetAll();
return gadgets;
}

public IEnumerable<Gadget> GetCategoryGadgets(string categoryName, string gadgetName = null)
{
var category = categoryRepository.GetCategoryByName(categoryName);
return category.Gadgets.Where(g => g.Name.ToLower().Contains(gadgetName.ToLower().Trim()));
}

public Gadget GetGadget(int id)
{
var gadget = gadgetsRepository.GetById(id);
return gadget;
}

public void CreateGadget(Gadget gadget)
{
gadgetsRepository.Add(gadget);
}

public void SaveGadget()
{
unitOfWork.Commit();
}

#endregion

}

第一个和最后一个突出显示的代码行提醒您为什么创建IUnitOfWork接口。如果我们想通过这个服务类创建一个小工具对象,我们可以这样写。
// init a gadget object..
gadgetService.CreateGadget(gadget);
gadgetService.SaveGadget();

其他突出显示的代码行表示此服务所需的任何存储库都将通过其构造函数注入。这将通过依赖容器完成,我们将使用Autofac框架在MVC项目的启动类中设置依赖容器。以同样的方式,我创建了GadgetService。cs文件。
// operations you want to expose
public interface ICategoryService
{
IEnumerable<Category> GetCategories(string name = null);
Category GetCategory(int id);
Category GetCategory(string name);
void CreateCategory(Category category);
void SaveCategory();
}

public class CategoryService : ICategoryService
{
private readonly ICategoryRepository categorysRepository;
private readonly IUnitOfWork unitOfWork;

public CategoryService(ICategoryRepository categorysRepository, IUnitOfWork unitOfWork)
{
this.categorysRepository = categorysRepository;
this.unitOfWork = unitOfWork;
}

#region ICategoryService Members

public IEnumerable<Category> GetCategories(string name = null)
{
if (string.IsNullOrEmpty(name))
return categorysRepository.GetAll();
else
return categorysRepository.GetAll().Where(c => c.Name == name);
}

public Category GetCategory(int id)
{
var category = categorysRepository.GetById(id);
return category;
}

public Category GetCategory(string name)
{
var category = categorysRepository.GetCategoryByName(name);
return category;
}

public void CreateCategory(Category category)
{
categorysRepository.Add(category);
}

public void SaveCategory()
{
unitOfWork.Commit();
}

#endregion
}
我们也完成了服务层的工作。让我们从最后一个ASP开始。NET MVC Web应用程序。

 

演示文稿层
添加新ASP。NET Web应用程序名为Store。Web选择选中MVC选项的空模板。我们还需要通过Nuget Packages添加对以前所有类库项目和实体框架安装的引用。你可能想知道,我们是否会在这个项目中编写任何与实体框架相关的查询?根本不需要,我们需要它的一些名称空间,这样我们就可以为应用程序设置数据库配置,例如数据库初始化器。自从我们开始做这个,打开Global.asax。cs文件,并添加下面一行代码来设置我们在Store中创建的种子初始化器。数据项目。
Glbal.asax.cs
protected void Application_Start()
{
// Init database
System.Data.Entity.Database.SetInitializer(new StoreSeedData());

AreaRegistration.RegisterAllAreas();
RouteConfig.RegisterRoutes(RouteTable.Routes);
}
您还需要创建一个连接字符串元素来定义要在哪里创建数据库。在Web中添加以下元素。配置文件,并根据您的开发环境要求对其进行更改。
Web.config
<connectionStrings>
<add name="StoreEntities" connectionString="Data Source=(localdb)\v11.0;Initial Catalog=StoreDb;Integrated Security=True" providerName="System.Data.SqlClient" />
</connectionStrings>
我们在前面的步骤中已经做了这样的努力来创建存储库和服务,但现在是时候让它们一起工作了。如果你还记得,所有服务构造器都有必须注入的存储库接口。服务主题稍后将注入控制器构造器,这就是我们的应用程序的工作方式。为了实现这一点,我们需要设置依赖注入,因此我决定使用Autofac。确保安装Autofac ASP。NET MVC 5通过Nuget包集成。
Create a Bootstrapper.cs file under the Start_App folder and paste the following code.
Bootstrapper.cs
public static void Run()
{
SetAutofacContainer();
}

private static void SetAutofacContainer()
{
var builder = new ContainerBuilder();
builder.RegisterControllers(Assembly.GetExecutingAssembly());
builder.RegisterType<UnitOfWork>().As<IUnitOfWork>().InstancePerRequest();
builder.RegisterType<DbFactory>().As<IDbFactory>().InstancePerRequest();

// Repositories
builder.RegisterAssemblyTypes(typeof(GadgetRepository).Assembly)
.Where(t => t.Name.EndsWith("Repository"))
.AsImplementedInterfaces().InstancePerRequest();
// Services
builder.RegisterAssemblyTypes(typeof(GadgetService).Assembly)
.Where(t => t.Name.EndsWith("Service"))
.AsImplementedInterfaces().InstancePerRequest();

IContainer container = builder.Build();
DependencyResolver.SetResolver(new AutofacDependencyResolver(container));
}
}
代码本身是不言自明的。我希望你已经跟随我,并且你已经像我一样命名了你的存储库和服务类,否则,这是行不通的。完成指导还有两件重要的事情要做。第一个定义ViewModel类并将Automapper设置为将域实体映射到视图模型并向后映射。第二个是了解如何在我们的web应用程序中设置CSS Bootstrap。我想大多数人都会从Nuget Packages安装引导程序,并开始向项目添加css和脚本引用。在这里,我们将采用不同的方法。

CSS引导

首先,从官方网站下载Boostrap发行版。在应用程序中分别添加三个名为css、fonts和js的文件夹。在css文件夹中粘贴引导程序。从您下载的css文件中,在字体文件夹中粘贴所有内容都在相应的字体文件夹中,在js文件夹中,只需粘贴引导程序即可。js文件。我们将使用捆绑和小型化进行引导,为了实现这一点,您需要安装Microsoft ASP。NET Web优化框架。
完成安装后,将名为BundleConfig的新类添加到App_Start文件夹中,如下所示:
BundleConfig.cs
public class BundleConfig
{
public static void RegisterBundles(BundleCollection bundles)
{
bundles.Add(new ScriptBundle("~/bootstrap/js").Include("~/js/bootstrap.js", "~/js/site.js"));
bundles.Add(new StyleBundle("~/bootstrap/css").Include("~/css/bootstrap.css", "~/css/site.css"));

BundleTable.EnableOptimizations = true;
}
}
正如你所看到的,我也参考了这个网站。js和站点。css javascript和css文件。这些文件可以承载您想要执行的任何引导css自定义或任何javascript相关代码。请随意添加相应的文件并将其保留为空。现在我们需要声明,我们希望MVC使用捆绑和小型化,因此在Global.asax中添加以下行。cs文件。
Global.asax.cs
protected void Application_Start()
{
// Init database
System.Data.Entity.Database.SetInitializer(new StoreSeedData());

AreaRegistration.RegisterAllAreas();
RouteConfig.RegisterRoutes(RouteTable.Routes);
BundleConfig.RegisterBundles(BundleTable.Bundles);

// Autofac and Automapper configurations
Bootstrapper.Run();
}
请注意,我还调用了Bootstrapper。Run()函数,用于设置我们之前所做的Autofac配置。此函数还将配置Automapper,稍后我们将看到这一点。让我们暂时结束Bootrap。我们需要一个Layout用于我们的应用程序,所以在Views文件夹下创建一个Shared文件夹,并添加一个名为_Layout.cshtml的MVC5LayoutPage(Razor)类型的新项目。
_Layout.cshtml
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>@ViewBag.Title</title>
<!-- Bootstrap -->
@Styles.Render("~/bootstrap/css")
<!--[if lt IE 9]>
<script src="https://oss.maxcdn.com/libs/html5shiv/3.7.0/
html5shiv.js"></script>
<script src="https://oss.maxcdn.com/libs/respond.js/1.4.2/
respond.min.js"></script>
<![endif]-->
</head>
<body>
<nav id="myNavbar" class="navbar navbar-default navbar-inverse navbar-fixed-top" role="navigation">
<!-- Brand and toggle get grouped for better mobile display -->
<div class="navbar-header">
<button type="button" class="navbar-toggle" data-toggle="collapse" data-target="#navbarCollapse">
<span class="sr-only">Toggle navigation</span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
@Html.ActionLink("Store", "Index", "Home", new { }, new { @class = "navbar-brand" })
</div>
<!-- Collect the nav links, forms, and other content for toggling -->
<div class="collapse navbar-collapse" id="navbarCollapse">
<ul class="nav navbar-nav">
<li class="active">
@Html.ActionLink("Tablets", "Index", "Home", new { category = "Tablets" }, null)
</li>
<li class="active">
@Html.ActionLink("Laptops", "Index", "Home", new { category = "Laptops" }, null)
</li>
<li class="active">
@Html.ActionLink("Mobiles", "Index", "Home", new { category = "Mobiles" }, null)
</li>
</ul>
</div>
</nav>
@RenderBody()
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.0/jquery.min.js"></script>
@Scripts.Render("~/bootstrap/js")
</body>
</html>
页面可能会抱怨无法解析Razor语法,因此您必须在web中添加以下using语句。位于Views文件夹下的config文件(而不是应用程序的web.config)。以下是该文件的一部分。
web.config
<namespaces>
<add namespace="System.Web.Mvc" />
<add namespace="System.Web.Mvc.Ajax" />
<add namespace="System.Web.Mvc.Html" />
<add namespace="System.Web.Routing" />
<add namespace="Store.Web" />
<add namespace="System.Web.Optimization" />
</namespaces>
自动映射器

在实际应用程序中,您的域对象可能有很多属性,但您只需要在浏览器中显示其中的一些属性。更重要的是,当发布回服务器时,例如当通过表单元素创建对象时,您还希望只发布域对象的一些属性。为此,您定义了ViewModel对象,并使用它们来代替真正的域对象。确保从Nuget Packages安装Automapper。
添加一个名为ViewModels的新文件夹,其中包含以下类。
GadgetViewModel.cs
public class GadgetViewModel
{
public int GadgetID { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public decimal Price { get; set; }
public string Image { get; set; }

public int CategoryID { get; set; }
}

CategoryViewModel.cs
public class CategoryViewModel
{
public int CategoryID { get; set; }
public string Name { get; set; }

public List<GadgetViewModel> Gadgets { get; set; }
}

GadgetFormViewModel.cs
public class GadgetFormViewModel
{
public HttpPostedFileBase File { get; set; }
public string GadgetTitle { get; set; }
public string GadgetDescription { get; set; }
public decimal GadgetPrice { get; set; }
public int GadgetCategory { get; set; }
}

当ViewModel类的属性被命名为各自的域对象时,Automapper足够聪明,可以通过默认约定进行映射。否则,您必须自己手动设置映射。请注意我添加的最后一个类,GadgetFormViewModel。我们可以进行一个转换,在“ViewModel”之前添加一个“Form”单词,这样我们就知道这种类型的视图模型是通过表单元素发布回服务器的。现在让我们配置映射。添加新文件夹Mappings并添加以下类文件。
AutoMapperConfiguration.cs
public class AutoMapperConfiguration
{
public static void Configure()
{
Mapper.Initialize(x =>
{
x.AddProfile<DomainToViewModelMappingProfile>();
x.AddProfile<ViewModelToDomainMappingProfile>();
});
}
}
我们还没有创建所需的配置文件,但我们稍后会创建。我想向您展示的是,您可以创建任意多个自动映射器配置文件,然后将它们添加到Mapper中。初始化函数。这里我们将定义两个配置文件,一个用于将域模型映射到ViewModel,另一个用于向后。在与前一个相同的文件夹中添加以下类。

DomainToViewModelMappingProfile.cs
public class DomainToViewModelMappingProfile : Profile
{
public override string ProfileName
{
get { return "DomainToViewModelMappings"; }
}

protected override void Configure()
{
Mapper.CreateMap<Category,CategoryViewModel>();
Mapper.CreateMap<Gadget, GadgetViewModel>();
}
}

ViewModelToDomainMappingProfile.cs
public class ViewModelToDomainMappingProfile : Profile
{
public override string ProfileName
{
get { return "ViewModelToDomainMappings"; }
}

protected override void Configure()
{
Mapper.CreateMap<GadgetFormViewModel, Gadget>()
.ForMember(g => g.Name, map => map.MapFrom(vm => vm.GadgetTitle))
.ForMember(g => g.Description, map => map.MapFrom(vm => vm.GadgetDescription))
.ForMember(g => g.Price, map => map.MapFrom(vm => vm.GadgetPrice))
.ForMember(g => g.Image, map => map.MapFrom(vm => vm.File.FileName))
.ForMember(g => g.CategoryID, map => map.MapFrom(vm => vm.GadgetCategory));
}
}
对于Domain->ViewModels映射,我们不需要设置任何内容。自动映射器将使用默认约定,这很好。对于我们的GadgetFormViewModel->Gadget映射,我们手动设置了如上所示的配置。Automapper要完成的最后一件事是在Bootstrapper类中添加以下行。
Bootsrapper.cs
public static class Bootstrapper
{
public static void Run()
{
SetAutofacContainer();
//Configure AutoMapper
AutoMapperConfiguration.Configure();
}
// Code ommitted

Controllers and Views
我们快完成了。添加名为HomeController的新MVC控制器并粘贴以下代码。
HomeController.cs
public class HomeController : Controller
{
private readonly ICategoryService categoryService;
private readonly IGadgetService gadgetService;

public HomeController(ICategoryService categoryService, IGadgetService gadgetService)
{
this.categoryService = categoryService;
this.gadgetService = gadgetService;
}

// GET: Home
public ActionResult Index(string category = null)
{
IEnumerable<CategoryViewModel> viewModelGadgets;
IEnumerable<Category> categories;

categories = categoryService.GetCategories(category).ToList();

viewModelGadgets = Mapper.Map<IEnumerable<Category>, IEnumerable<CategoryViewModel>>(categories);
return View(viewModelGadgets);
}
}
现在,您可以实际了解为什么我们如此努力地设置存储库、服务、Autofac和Automapper。对于每个请求,服务将被注入控制器中,并且它们的数据将在发送到客户端之前映射到ViewModels。右键单击“索引”操作并添加一个名为“索引”的视图,代码如下。我必须在这里提到,我们使用的小工具对象具有对Web应用程序项目中名为images的文件夹的图像引用。你可以使用你的图片,也可以在文章的最后下载这个项目。

Views/Home/Index.cshtml
@model IEnumerable<Store.Web.ViewModels.CategoryViewModel>

@{
ViewBag.Title = "Store";
Layout = "~/Views/Shared/_Layout.cshtml";
}

<p>

</p>
<div class="container">
<div class="jumbotron">

@foreach (var item in Model)
{
<div class="panel panel-default">
<div class="panel-heading">
@*@Html.DisplayFor(modelItem => item.Name)*@
@Html.ActionLink("View all " + item.Name, "Index", new { category = item.Name }, new { @class = "pull-right" })
@using (Html.BeginForm("Filter", "Home", new { category = item.Name }, FormMethod.Post, new { @class = "navbar-form" }))
{
@Html.TextBox("gadgetName", null, new { @class = "form-control", placeholder = "Search in " + item.Name })
}


</div>
@foreach (var gadget in item.Gadgets)
{
@Html.Partial("Gadget", gadget)
}
<div class="panel-footer">
@using (Html.BeginForm("Create", "Home", FormMethod.Post,
new { enctype = "multipart/form-data", @class = "form-inline" }))
{
@Html.Hidden("GadgetCategory", item.CategoryID)
<div class="form-group">
<label class="sr-only" for="file">File</label>
<input type="file" class="form-control" name="file" placeholder="Select picture..">
</div>
<div class="form-group">
<label class="sr-only" for="gadgetTitle">Title</label>
<input type="text" class="form-control" name="gadgetTitle" placeholder="Title">
</div>
<div class="form-group">
<label class="sr-only" for="gadgetName">Price</label>
<input type="number" class="form-control" name="gadgetPrice" placeholder="Price">
</div>
<div class="form-group">
<label class="sr-only" for="gadgetName">Description</label>
<input type="text" class="form-control" name="gadgetDescription" placeholder="Description">
</div>
<button type="submit" class="btn btn-primary">Upload</button>
}
</div>
</div>
}

</div>

</div>
这里有两件事需要注意。第一个是我们需要创建一个Partial视图来显示GadgetViewModel对象,第二个是窗体的控件元素的名称。您可以看到,它们使用了我们的GadgetFormViewModel属性。在共享文件夹下,创建以下部分视图以显示GadgetViewModel对象。
Views/Shared/Gadget.cshtml
@model Store.Web.ViewModels.GadgetViewModel

<div class="panel-body">
<div class="media">
<a class="pull-left" href="#">
<img class="media-object" src="../../images/@Model.Image" />
</a>
<div class="media-body">
<h3 class="media-heading">
@Model.Name
</h3>
<p>@Model.Description</p>
</div>
</div>
</div>
In the Index.cshtml page I have added search and filter functionality and Create gadget as well. To achieve that you need to add the following Action methods to the HomeController.

HomeController.cs
public ActionResult Filter(string category, string gadgetName)
{
IEnumerable<GadgetViewModel> viewModelGadgets;
IEnumerable<Gadget> gadgets;

gadgets = gadgetService.GetCategoryGadgets(category, gadgetName);

viewModelGadgets = Mapper.Map<IEnumerable<Gadget>, IEnumerable<GadgetViewModel>>(gadgets);

return View(viewModelGadgets);
}

[HttpPost]
public ActionResult Create(GadgetFormViewModel newGadget)
{
if (newGadget != null && newGadget.File != null)
{
var gadget = Mapper.Map<GadgetFormViewModel, Gadget>(newGadget);
gadgetService.CreateGadget(gadget);

string gadgetPicture = System.IO.Path.GetFileName(newGadget.File.FileName);
string path = System.IO.Path.Combine(Server.MapPath("~/images/"), gadgetPicture);
newGadget.File.SaveAs(path);

gadgetService.SaveGadget();
}

var category = categoryService.GetCategory(newGadget.GadgetCategory);
return RedirectToAction("Index", new { category = category.Name });
}
I am sure that at this point you understand the purpose of all the above code so I won’t explain anything. You need to add a Filter page so right click in the Filter action and create the following View.

Home/Views/Filter.cshtml
@model IEnumerable<Store.Web.ViewModels.GadgetViewModel>

@{
ViewBag.Title = "Filter";
Layout = "~/Views/Shared/_Layout.cshtml";
}

<div class="container">
<div class="jumbotron">

@foreach (var item in Model)
{
<div class="panel panel-default">
<div class="panel-heading">
@Html.Label(item.Name)
</div>
@Html.Partial("Gadget", item)
</div>
}

</div>
</div>

标签:架构,Name,get,void,class,最佳,MVC,new,public
From: https://www.cnblogs.com/5x19/p/17643567.html

相关文章

  • 以二进制文件安装K8S之高可用部署架构
    在Kubernetes系统中,Master节点扮演着总控中心的角色,通过不间断地与各个工作节点(Node)通信来维护整个集群的健康工作状态,集群中各资源对象的状态则被保存在etcd数据库中。在正式环境中应确保Master的高可用,并启用安全访问机制,至少包括以下几方面。Master的kube-apiserver、kube-c......
  • 2-10-Feign-最佳实践分析(11-Feign-实现Feign最佳实践)
    所谓的最佳实践是针对发请求与收请求两个接口而言的总共分两种规范:继承+抽取由于继承会出现多次实现且不同模块的维护人还不一样要是出现更新人力安排也是一个问题抽取方式则不会出现这些问题因为实现仅一份而且还都是由服务维护方维护的不存在人力安排问题从生产者......
  • 【PACS源码】认识PACS的架构和工作流程
     (一)PACS系统的组成及架构   PACS系统的基本组成部分包括:数字影像采集、通讯和网络、医学影像存储、医学影像管理、各类工作站五个部分。   而目前PACS系统的软件架构选型上看,主要有C/S和B/S两种形式。   C/S架构,即Client/Server(客户机/服务器)架构,将运算......
  • 如何快速画出一幅漂亮的架构图
    这篇文章总结了常用的架构图类型,可以借鉴笔者提供的模板,快速地产出符合业务需要的架构图。为什么要画好一幅架构图?一幅漂亮的架构图既是创作者的深度结构化思考和表达,对于读者来说也更加容易理解架构所要表达的意思。然而不擅长画图的程序员,在大脑里已经有了思路,如何快速能够......
  • 如何快速画出一幅漂亮的架构图
    这篇文章总结了常用的架构图类型,可以借鉴笔者提供的模板,快速地产出符合业务需要的架构图。为什么要画好一幅架构图?一幅漂亮的架构图既是创作者的深度结构化思考和表达,对于读者来说也更加容易理解架构所要表达的意思。然而不擅长画图的程序员,在大脑里已经有了思路,如何快速能够......
  • 如何快速画出一幅漂亮的架构图
    这篇文章总结了常用的架构图类型,可以借鉴笔者提供的模板,快速地产出符合业务需要的架构图。为什么要画好一幅架构图?一幅漂亮的架构图既是创作者的深度结构化思考和表达,对于读者来说也更加容易理解架构所要表达的意思。然而不擅长画图的程序员,在大脑里已经有了思路,如何快速能够......
  • 现代CSS布局技术:Flexbox和Grid的使用指南和最佳实践
    在现代Web开发中,CSS布局技术变得越来越重要。Flexbox和Grid是两种常用的CSS布局技术,它们可以帮助我们创建灵活、可维护的布局。Flexbox布局Flexbox是一种基于弹性盒子模型的布局技术,它可以让我们更加容易地创建水平或垂直方向上的布局。以下是Flexbox的常见属性:容器属性disp......
  • 管理员必看!Salesforce用户管理的7个最佳实践
    用户对象是在Salesforce中经常使用到的关键标准对象之一,熟悉用户管理以及对象的特殊性至关重要。本篇文章将探讨作为管理员可以做些什么,不仅可以保持对Salesforce及其相关数据的受控访问,还可以为你的用户充分利用功能。01Salesforce中的用户类型根据你在组织内使用的Salesfor......
  • 智安网络|零信任安全框架:保障数字化时代网络安全的最佳实践
    随着数字化时代的快速发展,网络安全问题变得越来越突出。传统的安全防御模式已经不再适用于现代复杂的网络环境中。为了应对日益增长的网络威胁,零信任安全模式应运而生。一、什么是零信任?零信任是一种安全框架和哲学,它基于一个简单的原则:不信任任何用户或设备,即使它们已经位于网络内......
  • 浅谈架构
    1     引言    笔者从事架构师工作多年,发现虽然软件开发人员人人都知道架构,但架构真正做什么,确很少有人能说的清楚。    大部分普通开发人员所想到的架构是框架的搭建以及各种架构技术比如缓存、消息队列、多线程等等,笔者曾经面试过一个应聘架构师岗位的......