我正在尝试创建一个Controller类来处理我将来最终创建的所有可预见的调查。目前我有一个'调查'表,其中包含以下字段: Id , SurveyName , Active 。在'主'调查'索引页面上,我列出了该表中找到的每个SurveyName。每个SurveyName都是可单击的,当单击时,页面将SurveyName作为字符串发送到接收控制器操作。所述控制器动作如下所示:
//
//GET: /Surveys/TakeSurvey/
public ActionResult TakeSurvey(string surveyName)
{
Assembly thisAssembly = Assembly.GetExecutingAssembly();
Type typeToCreate = thisAssembly.GetTypes().Where(t => t.Name == surveyName).First();
object newSurvey = Activator.CreateInstance(typeToCreate);
ViewBag.surveyName = surveyName;
return View(surveyName, newSurvey);
}
使用反射我能够创建由传入字符串'surveyName'指定的类型(Model)的新实例,并且能够将该模型传递给具有相同名称的视图。
示例
有人点击“SummerPicnic”,字符串“SummerPicnic”被传递给控制器。控制器使用反射创建SummerPicnic类的新实例,并将其传递给具有相同名称的视图。然后,一个人可以为他们的夏季野餐计划填写表格。
这一切都很好,花花公子。我坚持的部分是试图将POST方法传回的表单保存到正确的相应DB表中。由于我事先并不知道控制器会取回什么样的模型,我不仅不知道如何告诉它要保存哪种模型,还要把它保存到哪里,因为我可以做一些荒谬的事情,如:
//
//POST: Surveys/TakeSurvey
[HttpPost]
public ActionResult TakeSurvey(Model survey)
{
if (ModelState.IsValid)
{
_db. + typeof(survey) + .Add(survey);
_db.SaveChanges();
return RedirectToAction("Index", "Home");
}
return View();
}
有没有办法做到这一点,还是我应该从一个不同的角度来解决这个问题?我的最终目标是让一个控制器协调每一个简单的调查,所以我不必为我最后开展的每一项调查创建一个单独的控制器。
我能想到的另一种解决方案是为每个调查都有一个单独的方法,并在每个调查的视图中定义调用哪个方法。例如,如果我进行了SummerPicnic调查,则提交按钮会调用名为“SummerPicnic”的ActionMethod:
@Ajax.ActionLink("Create", "SummerPicnic", "Surveys", new AjaxOptions { HttpMethod = "POST" })
对PartyAttendance的调查会调用ActionMethod'PartyAttendance'等等。我宁愿不必这样做,但是......
更新1 我打电话的时候:
_db.Articles.Add(article);
_db.SaveChanges();
这是_ db :
private IntranetDb _db = new IntranetDb();
哪个是......
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Data.Entity;
using System.Data.Entity.ModelConfiguration.Conventions;
namespace Intranet.Models
{
public class IntranetDb : DbContext
{
public DbSet<Article> Articles { get; set; }
public DbSet<ScrollingNews> ScrollingNews { get; set; }
public DbSet<Survey> Surveys { get; set; }
public DbSet<Surveys.test> tests { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Conventions.Remove<PluralizingTableNameConvention>();
}
}
}
答案 0 :(得分:2)
您可以尝试这样的事情,
<强>更新强>
内置UpdateModel
将使用通用模型,请参阅此post,这样我们就可以完成更多工作。
[HttpPost]
public ActionResult TakeSurvey(FormCollection form, surveyName)
{
var surveyType = Type.GetType(surveyName);
var surveyObj = Activator.CreateInstance(surveyType);
var binder = Binders.GetBinder(surveyType);
var bindingContext = new ModelBindingContext()
{
ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(() => surveyObj, surveyType),
ModelState = ModelState,
ValueProvider = form
};
binder.BindModel(ControllerContext, bindingContext);
if (ModelState.IsValid)
{
// if "db" derives from ObjectContext then..
db.AddObject(surveyType, surveyObj);
db.SaveChanges();
// if "db" derives from DbContext then..
var objCtx = ((IObjectContextAdapter)db).ObjectContext;
objCtx.AddObject(surveyType, surveyObj);
db.SaveChanges();
return RedirectToAction("Index", "Home");
}
return View();
}
检查this两个人是否知道DbContext
和ObjectContext
答案 1 :(得分:2)
我最终得到了Mark代码的略微修改版本:
[HttpPost]
public ActionResult TakeSurvey(string surveyName, FormCollection form)
{
//var surveyType = Type.GetType(surveyName);
//var surveyObj = Activator.CreateInstance(surveyType);
// Get survey type and create new instance of it
var thisAssembly = Assembly.GetExecutingAssembly();
var surveyType = thisAssembly.GetTypes().Where(t => t.Name == surveyName).First();
var newSurvey = Activator.CreateInstance(surveyType);
var binder = Binders.GetBinder(surveyType);
var bindingContext = new ModelBindingContext()
{
ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(() => newSurvey, surveyType),
ModelState = ModelState,
ValueProvider = form
};
binder.BindModel(ControllerContext, bindingContext);
if (ModelState.IsValid)
{
var objCtx = ((IObjectContextAdapter)_db).ObjectContext;
objCtx.AddObject(surveyName, newSurvey);
_db.SaveChanges();
return RedirectToAction("Index", "Home");
}
return View();
}
当 surveyType 设置为 Type.GetType(surveyName); 时,我遇到了 surveyType ,所以我继续前进并通过Reflection检索了Type。 / p>
我遇到的唯一麻烦就是:
if (ModelState.IsValid)
{
var objCtx = ((IObjectContextAdapter)_db).ObjectContext;
objCtx.AddObject(surveyName, newSurvey);
_db.SaveChanges();
return RedirectToAction("Index", "Home");
}
当它尝试AddObject时,我收到异常“无法找到EntitySet名称'IntranetDb.test'。”我只需要弄清楚前缀'IntranetDb'。希望我会做生意。
<强>更新强>
我完全忽视的一件事是从视图中将模型传递给控制器......哦,麻烦。我目前有一个ActionLink取代了正常的“提交”按钮,因为我不知道如何向控制器传递创建正确的Survey模型实例所需的字符串:
<p>
@Ajax.ActionLink("Create", "TakeSurvey", "Surveys", new { surveyName = ViewBag.surveyName }, new AjaxOptions { HttpMethod = "POST" })
@*<input type="submit" value="Create" />*@
</p>
因此,一旦我弄清楚如何将'IntranetDb.test'变为'测试',我将解决如何在提交时使调查字段全部为“空”。
更新2
我将提交方法从使用Ajax ActionLink更改为普通提交按钮。在我意识到Mark的bindingContext正在为我做绑定(将表单值注入到Model值)之后,为我的Model值设置了这个固定的空值。所以现在我的View提交了一个简单的:
<input type="submit" value="Submit" />
回过头来弄清楚如何将'IntranetDb.test'截断为'测试'......
得到它
问题在于我的IntranetDb类:
public class IntranetDb : DbContext
{
public DbSet<Article> Articles { get; set; }
public DbSet<ScrollingNews> ScrollingNews { get; set; }
public DbSet<SurveyMaster> SurveyMaster { get; set; }
public DbSet<Surveys.test> tests { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Conventions.Remove<PluralizingTableNameConvention>();
}
}
objCtx.AddObject(surveyName,newSurveyEntry); 正在IntranetDb类中查找名为“test”的条目(“EntitySet”)。问题在于我没有一个名为“test”的EntitySet,而是一个带有's'的“tests”的名称用于复数。事实证明我根本不需要截断任何东西,我只需要指向正确的对象:P一旦我顺利完成,我就应该做生意了!感谢Mark和Abhijit的帮助! ^ _ ^
结束
//
//POST: Surveys/TakeSurvey
[HttpPost]
public ActionResult TakeSurvey(string surveyName, FormCollection form)
{
//var surveyType = Type.GetType(surveyName);
//var surveyObj = Activator.CreateInstance(surveyType);
// Create Survey Type using Reflection
var thisAssembly = Assembly.GetExecutingAssembly();
var surveyType = thisAssembly.GetTypes().Where(t => t.Name == surveyName).First();
var newSurveyEntry = Activator.CreateInstance(surveyType);
// Set up binder
var binder = Binders.GetBinder(surveyType);
var bindingContext = new ModelBindingContext()
{
ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(() => newSurveyEntry, surveyType),
ModelState = ModelState,
ValueProvider = form // Get values from form
};
var objCtx = ((IObjectContextAdapter)_db).ObjectContext;
// Retrieve EntitySet name for Survey type
var container = objCtx.MetadataWorkspace.GetEntityContainer(objCtx.DefaultContainerName, DataSpace.CSpace);
string setName = (from meta in container.BaseEntitySets
where meta.ElementType.Name == surveyName
select meta.Name).First();
binder.BindModel(ControllerContext, bindingContext); // bind form values to survey object
if (ModelState.IsValid)
{
objCtx.AddObject(setName, newSurveyEntry); // Add survey entry to appropriate EntitySet
_db.SaveChanges();
return RedirectToAction("Index", "Home");
}
return View();
}
它有点臃肿,但它现在有效。 This post帮助我从Survey对象本身获取EntitySet,因此我不必担心建立某种EntitySet命名约定。
答案 2 :(得分:0)
我看到的主要问题是将模型绑定到TakeSurvey
POST方法。如果你想要不同类型的调查模型应该由这个方法处理,MVC应该在调用动作之前绑定到这个模型,我相信你可以在所有这样的通用模型上有一个包装模型类,比如说SurveyModel
并使用自定义模型绑定器绑定到这些模型。
public class SurveyModel
{
public string GetSurveyModelType();
public SummerPicnicSurvey SummerPicnicSurvey { get; set; }
public PartyAttendanceSurvey PartyAttendanceSurvey { get; set; }
}
然后编写自定义mobel绑定器来绑定此模型。从请求表单字段中,我们可以看到发布了什么类型的调查模型,然后相应地获取所有字段并初始化SurveyModel类。如果发布了SummerPicnicSurvey,则将使用此类设置类SurveyModel,并且PartyAttendanceSurvey将为null。 Example custom model binder.
从控制器操作TakeSurvey
POST方法,您可以像这样更新数据库:
[HttpPost]
public ActionResult TakeSurvey(SurveyModel survey)
{
if (ModelState.IsValid)
{
if(survey.GetSurveyModelType() == "SummerPicnicSurvey")
_db.UpdateSummerPicnicSurvey(survey.SummerPicnicSurvey);
else if (survey.GetSurveyModelType() == "PartyAttendanceSurvey")
_db.UpdateSummerPicnicSurvey(survey.PartyAttendanceSurvey);
_db.SaveChanges();
return RedirectToAction("Index", "Home");
}
return View();
}
代替SurveyModel封装其他调查,您可以继承并使用.net as
进行类型检查并使用模型。
话虽如此,我认为对每种模型使用不同的方法没有坏处。这将使您能够很好地对代码进行单元测试。太多,否则维持不健康。或者,您可以将通用模型SurveyModel
传输到存储库或数据访问层,并让它以多态方式处理它。我更喜欢更小的功能并保持代码清洁。
编辑:继承方式:
public class SurveyModel
{
public virtual bool Save();
}
public partial class SummerPicnicSurvey : SurveyModel
{
public bool Save(SummerPicnicSurvey survey)
{
using(var _dbContext = new MyContext())
{
_dbContex.SummerPicnicSurveys.Add(survey);
_dbContex.SaveChanges();
}
}
}
[HttpPost]
public ActionResult TakeSurvey(SurveyModel survey)
{
if (ModelState.IsValid)
{
survey.Save();
return RedirectToAction("Index", "Home");
}
return View();
}
您添加的任何新的Survey模型类型都必须实现SaveChanges或Save方法,这将调用正确的dbcontext方法。控制器操作只会在传递给它的泛型`SurveyModel'引用上调用Save。因此,该行动将被关闭以进行修改,但可以进行修改。开放式设计原则。