0

我正在尝试创建一个 Controller 类来处理我将在未来最终创建的所有可预见的调查。目前,我有一个“调查”表,其中包含以下字段:IdSurveyNameActive。在“主”调查索引页面上,我列出了在该表中找到的每个调查名称。每个 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”指定的类型(模型)的新实例,并且能够将该模型传递给具有相同名称的视图。

示例
有人点击“SummerPicnic”,字符串“SummerPicnic”被传递给控制器​​。控制器使用反射创建 SummerPicnic 类的新实例并将其传递给具有相同名称的视图。然后,一个人可以为他们的夏季野餐计划填写表格。

这一切都很好,花花公子。我坚持的部分是尝试将 POST 方法传回的表单保存到正确的相应数据库表中。由于我不提前知道控制器将返回哪种模型,所以我不仅不知道如何告诉它要保存哪种模型,而且也不知道将它保存到哪里,因为我可以不要做一些荒谬的事情,比如:

    //
    //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>();
        }
    }
}
4

3 回答 3

2

你可以试试这样的

更新:

内置UpdateModel将与通用模型一起使用,请参阅这篇文章,所以我们要做的工作不多。

[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();
}

检查DbContext两个知道和之间的区别ObjectContext

于 2012-06-16T03:54:16.243 回答
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();
    }

当调查类型设置为Type.GetType(surveyName);,我遇到了“null”。所以我继续通过反射检索类型。

我现在遇到的唯一麻烦是这里:

        if (ModelState.IsValid)
        {
            var objCtx = ((IObjectContextAdapter)_db).ObjectContext;
            objCtx.AddObject(surveyName, newSurvey);
            _db.SaveChanges();
            return RedirectToAction("Index", "Home");
        }

当它尝试添加对象时,我收到异常“找不到实体集名称'IntranetDb.test'。” 我只需要弄清楚去掉前缀“IntranetDb”。希望我能做生意。

更新
我完全忽略的一件事是将模型从视图传递给控制器​​......哦,麻烦了。我目前有一个 ActionLink 替换了普通的“提交”按钮,因为我不确定如何将创建正确的调查模型实例所需的字符串传递给控制器​​:

    <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 正在为我进行绑定(将表单值注入到模型值上)之后,为我的模型值设置了这个固定的空值。所以现在我的视图提交了一个简单的:

<input type="submit" value="Submit" />

回到弄清楚如何将'IntranetDb.test'截断为'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,而是一个名为“tests”的实体集,并带有一个's'表示复数。结果我根本不需要截断任何东西,我只需要指向正确的对象: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();
    }

它有点臃肿,但它现在有效。 这篇文章帮助我从 Survey 对象本身获取 EntitySet,因此我无需担心建立某种 EntitySet 命名约定。

于 2012-06-18T19:44:23.113 回答
0

我看到的主要问题是将模型绑定到TakeSurveyPOST 方法。如果您希望此方法应处理不同类型的调查模型,并且 MVC 应在调用操作之前绑定到此模型,我相信您可以在所有此类通用模型上拥有一个包装模型类,例如SurveyModel并使用自定义模型绑定器绑定到这些模型。

public class SurveyModel
{
    public string GetSurveyModelType();
    public SummerPicnicSurvey SummerPicnicSurvey { get; set; }
    public PartyAttendanceSurvey PartyAttendanceSurvey { get; set; }
}

然后编写一个自定义的 mobel binder 来绑定这个模型。从请求表单字段中,我们可以看到发布了哪种类型的调查模型,然后相应地获取所有字段并初始化 SurveyModel 类。如果 SummerPicnicSurvey 已发布,则类 SurveyModel 将设置为此类,并且 PartyAttendanceSurvey 将为空。示例自定义模型绑定器。

从控制器操作TakeSurveyPOST 方法中,您可以像这样更新 db:

 [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 封装其他调查,您可以继承并使用 .netas通过检查进行类型转换并使用模型。

话虽如此,我认为对每个模型使用不同的方法并没有什么坏处。这将使您能够很好地对代码进行单元测试。如果其他情况太多,维护起来不健康。或者,您可以将通用模型转移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();
   }

您添加的任何新的调查模型类型都必须实现 SaveChanges 或 Save 方法,这将调用正确的 dbcontext 方法。控制器操作只会在传递给它的通用“SurveyModel”引用上调用 Save。因此,该操作将对修改关闭,但对修改开放。开闭设计原则。

于 2012-06-16T04:16:18.703 回答