【问题标题】:Generic View and Controller to avoid Duplicate Code in MVC通用视图和控制器以避免 MVC 中的重复代码
【发布时间】:2019-04-01 15:21:06
【问题描述】:

我的 MVC 项目中有很多具有相同基本结构的模型。所以,我创建了一个像下面这样的大师班。

    public class MasterTemplate
    {
        [Key]
        public int Id { get; set; }

        [Required]
        [StringLength(255)]
        public string Description { get; set; }
        public DateTime? UpdatedOn { get; set; }
        public string UpdatedBy { get; set; }
    }

我创建了所有模型类,如下所示。

    public class Industry : MasterTemplate
    {

    }

    public class Caste : MasterTemplate
    {

    }

    public class Gender : MasterTemplate
    {

    }

    public class Qualification : MasterTemplate
    {

    }

    public class BloodGroup: MasterTemplate
    {

    }

这样的还有很多。以下是我的 IndustryController 代码。

 public class IndustryController : Controller
    {
        private ApplicationDbContext _context { get; set; }
        private string UserId { get; set; }

        public IndustryController()
        {
            _context = new ApplicationDbContext();
            UserId = System.Web.HttpContext.Current.User.Identity.GetUserId();
        }


        public ActionResult Index(int id = 0)
        {
            Industry data = new Industry();
            if (id > 0)
                data = _context.Industries.SingleOrDefault(c => c.Id == id);
            if (data == null)
                data = new Industry();

            return View("Industry", data);
        }

        [HttpPost]
        [ValidateAntiForgeryToken]
        public ActionResult Save(Industry data)
        {
            if (!ModelState.IsValid)
                return View("Industry", data);

            var record = _context.Industries.Where(c => c.Description.Trim().ToLower() == data.Description.Trim().ToLower() && c.Id != data.Id);
            if (record.Count() > 0)
            {
                ModelState.AddModelError("Duplicate Industry", "Industry already exist");
                return View("Industry", data);
            }

            Industry cm = new Industry();
            if (data.Id >= 1)
            {
                cm = _context.Industries.SingleOrDefault(c => c.Id == data.Id);
                cm.Description = data.Description;
                cm.UpdatedOn = DateTime.Now;
                cm.UpdatedBy = UserId;
            }
            else
            {
                cm = data;
                _context.Industries.Add(cm);
            }
            _context.SaveChanges();


            return RedirectToAction("Index", new { id = 0 });

        }

以下是我的 IndustryView 代码

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

<h3>Industries Management</h3>
<div class="row">
    <div class="col-md-4">
        @using (@Html.BeginForm("Save", "Industry"))
        {
            @Html.ValidationSummary("Please correct the following")
            @Html.HiddenFor(m => m.Id)

            <div class="form-group">
                <div>
                    @Html.LabelFor(m => m.Description)
                    @Html.TextBoxFor(m => m.Description, new { @class = "form-control", autocomplete = "off" })
                    @Html.ValidationMessageFor(m => m.Description)
                </div>
            </div>
            @Html.AntiForgeryToken()
            <button type="submit" class="btn btn-primary btn-sm">Save</button>
        }
    </div>
    <div class="col-md-8">
        <table class="table table-sm" id="mydata">
            <thead>
                <tr>
                    <th>
                        Industries
                    </th>
                    <th>

                    </th>
                </tr>
            </thead>
            <tbody></tbody>
        </table>
    </div>
</div>


@section scripts
{
    @Scripts.Render("~/bundles/jqueryval")
    <script>
        $(document).ready(function () {
            $("#mydata").DataTable({
                ajax: {
                    url: "/api/get/industries",
                    dataSrc: ""
                },
                columns: [
                    {
                        data: "description"
                    },
                    {
                        data: "id",
                        render: function (data) {
                            var url = '@Url.Action("Index", "Industry", new { id = "__data__" })';
                            return '<a href="' + url.replace('__data__', data) + '">Edit</a>';
                        }
                    }
                ]
            });
        });
    </script>
}

现在我的问题是,我项目中所有模型的控制器和视图代码几乎相似。如上。因此,我想概括它们并创建一个可用于我所有其他模型的控制器和视图。我是泛型新手,尝试了以下代码,但仍然无法弄清楚前进的方向。这让我很困惑。

    public interface IMaster
    {
        int Id { get; set; }
        string Description { get; set; }
    }

 public class GenericController : Controller
    {
        private ApplicationDbContext _context { get; set; }
        private string UserId { get; set; }

        public GenericController()
        {
            _context = new ApplicationDbContext();
            UserId = System.Web.HttpContext.Current.User.Identity.GetUserId();
        }


        public ActionResult Index(int id = 0)
        {
            IMaster data = null;
            if (id > 0)
                data = _context.Industries.SingleOrDefault(c => c.Id == id);
            if (data == null)
                data = new Industry();

            return View("Generic", data);
        }

        [HttpPost]
        [ValidateAntiForgeryToken]
        public ActionResult Save(IMaster data)
        {
            if (!ModelState.IsValid)
                return View("Generic", data);

            var record = _context.Industries.Where(c => c.Description.Trim().ToLower() == data.Description.Trim().ToLower() && c.Id != data.Id);
            if (record.Count() > 0)
            {
                ModelState.AddModelError("Duplicate Industry", "Industry already exist");
                return View("Generic", data);
            }

            Industry cm = new Industry();
            if (data.Id >= 1)
            {
                cm = _context.Industries.SingleOrDefault(c => c.Id == data.Id);
                cm.Description = data.Description;
                cm.UpdatedOn = DateTime.Now;
                cm.UpdatedBy = UserId;
            }
            else
            {
                cm.Id = data.Id;
                cm.Description = data.Description;
                _context.Industries.Add(cm);
            }
            _context.SaveChanges();


            return RedirectToAction("Index", new { id = 0 });

        }
    }

有人可以指导我正确的方向吗,需要为我的项目中的所有类似模型创建一个通用控制器和视图。

【问题讨论】:

  • 好问题,我之前问过一个very similar question 白人,这可能会有所帮助。
  • 哇,我认为我们都遵循相同的模式,我从这个问题中删除了所有存储库,工作模式代码单元以保持简单,请在此处查看我的其他问题,请检查您是否有对此的答案,将继续阅读您的其他答案stackoverflow.com/questions/55350766/…
  • 我不确定 asp.net mvc 是否允许我们创建通用控制器或自定义通用视图,但我认为如果您创建通用控制器或视图,您也需要自定义 ioc 管理器。

标签: c# asp.net-mvc generics interface


【解决方案1】:

我没有运行它,但我很有信心,这应该可以解决问题!实际上唯一真正通用的部分是控制器。其他东西只是通常的多态性。谢谢你的灵感。考虑这样的解决方案很有趣。也许我会在未来建立类似的东西。

请注意:您将控制器的名称绑定到每个模型的名称。请注意这一点!必须保留一个命名架构,否则您会破坏它。

公共类 [ModelName]Controller : MasterController
{
}

ajax 端点将结束具有 [PluralName] 的值
(在视图中继续阅读以了解我的意思。)

您将需要 MasterTemplate 中的附加属性。理想情况下使其抽象化,这样您就不会忘记在派生类中实现它。这是视图标题中的复数名称和视图中的 ajax 调用。

public abstract class MasterTemplate
{
    [Key]
    public int Id { get; set; }

    public abstract string PluralName {get;}

    [Required]
    [StringLength(255)]
    public string Description { get; set; }
    public DateTime? UpdatedOn { get; set; }
    public string UpdatedBy { get; set; }
}

行业会变成这样

public class Industry: MasterTemplate
{
  public override string PluralName => "Industries"
}

制作一个真正通用的控制器并从中派生所有其他控制器

public class IndustryController : MasterController<Industry>
{
   //done everthing else is in the master :)
}

这里是通用的 MasterController

public class MasterController<T> : Controller where T : MasterTemplate, new()
{
    private ApplicationDbContext _context { get; set; }
    private string UserId { get; set; }

    public MasterController()
    {
        _context = new ApplicationDbContext();
        UserId = System.Web.HttpContext.Current.User.Identity.GetUserId();
    }


    public ActionResult Index(int id = 0)
    {
        T data = (id > 0) 
               ? data = _context.Set<T>().SingleOrDefault(c => c.Id == id) ?? new T() 
               : new T();

        return View("View", data);
    }

    [HttpPost]
    [ValidateAntiForgeryToken]
    public ActionResult Save(T data)
    {
        if (!ModelState.IsValid)
            return View("View", data);

        var record = _context.Set<T>().Where(c => c.Description.Trim().ToLowerInvariant() == data.Description.Trim().ToLowerInvariant() && c.Id != data.Id);
        if (record.Count() > 0)
        {
            ModelState.AddModelError($"Duplicate {typeof(T).Name}", $"{typeof(T).Name} already exist");
            return View("View", data);
        }

        if (data.Id >= 1)
        {
            T cm = _context.Set<T>().SingleOrDefault(c => c.Id == data.Id);
            cm.Description = data.Description;
            cm.UpdatedOn = DateTime.Now;
            cm.UpdatedBy = UserId;
        }
        else
        {
            _context.Set<T>().Add(data);
        }
        _context.SaveChanges();


        return RedirectToAction("Index", new { id = 0 });

    }

将视图命名为“视图”(或与您在 MasterController 中的名称相同)并将其放置在共享文件夹中,以便每个控制器都能在其中找到它。

@model MasterTemplate
@{
    string name = Model.GetType().Name;
    ViewBag.Title = name;
    Layout = "~/Views/Shared/_Layout.cshtml";
}

<h3>@Model.PluralName Management</h3>
<div class="row">
    <div class="col-md-4">
        @using (@Html.BeginForm("Save", name))
        {
            @Html.ValidationSummary("Please correct the following")
            @Html.HiddenFor(m => m.Id)

            <div class="form-group">
                <div>
                    @Html.LabelFor(m => m.Description)
                    @Html.TextBoxFor(m => m.Description, new { @class = "form-control", autocomplete = "off" })
                    @Html.ValidationMessageFor(m => m.Description, $"{name} is required.", new { @class = "text-danger" })
                </div>
            </div>
            @Html.AntiForgeryToken()
            <button type="submit" class="btn btn-primary btn-sm">Save</button>
        }
    </div>
    <div class="col-md-8">
        <table class="table table-sm" id="mydata">
            <thead>
                <tr>
                    <th>
                        @(name)
                    </th>
                    <th>

                    </th>
                </tr>
            </thead>
            <tbody></tbody>
        </table>
    </div>
</div>


@section scripts
{
    @Scripts.Render("~/bundles/jqueryval")
    <script>
        $(document).ready(function () {
            $("#mydata").DataTable({
                ajax: {
                    url: "/api/get/@(Model.PluralName)",
                    dataSrc: ""
                },
                columns: [
                    {
                        data: "description"
                    },
                    {
                        data: "id",
                        render: function (data) {
                            var url = '@Url.Action("Index", "@(name)", new { id = "__data__" })';
                            return '<a href="' + url.replace('__data__', data) + '">Edit</a>';
                        }
                    }
                ]
            });
        });
    </script>
}

【讨论】:

  • 我不能 100% 确定,如果 @name 在整个视图中可用。如果不是,请将“name”替换为“Model.GetType().Name”。
  • 非常感谢您的回答,.GetTable() 给出错误,我应该导入任何缺少的命名空间。
  • 用 .Set() 替换它,它会在完全构建后告诉你它是否工作
  • 这也很简单:tutorialsteacher.com/mvc/htmlhelper-validationmessagefor,我相应地编辑了帖子。我在 ValidationMessageFor() 中选择了 ErrorMessage 而不是RequiredAttribute,因为你想要的只是替换项目的名称。
  • 顺便说一句:我最初对您的“模板”进行了尽可能少的更改,但您应该考虑使用 ToLowerInvariant() 而不是 ToLower()。我在这样做时遇到了一些问题(但与 SQL 无关)。土耳其语小写“i”是一个特殊的 unicode 字符,而不是我们通常的“i”。但话又说回来,它被翻译成不区分大小写的 SQL,所以它甚至应该在没有 ToLower() 的情况下工作。至少值得测试。
猜你喜欢
  • 2017-06-16
  • 1970-01-01
  • 2018-07-07
  • 2016-05-06
  • 1970-01-01
  • 2015-12-06
  • 2012-05-10
  • 1970-01-01
  • 2010-09-27
相关资源
最近更新 更多