GRU119
GRU119

Reputation: 1128

ASP.NET MVC 5 - How to Add [Required] Data Annotation in ViewModel

I'm calling three Models (Unit, Site, Work_Type) in my view model called UnitAdminViewModel. I need to set one field as required from the Unit Model. Since I'm using Database First approach, I cannot modify the Unit Model directly since this gets autogenerated. How can I successfully add:

[Required(ErrorMessage = "Group is required")] public string GroupName { get; set; }

to my view model UnitAdminViewModel?

public class UnitAdminViewModel
{
    public Unit Unit { get; set; }
    public List<Site> Site { get; set; }
    public IEnumerable<Work_Type> Work_Type { get; set; }
}

In the Unit Model, I want to set the field GroupName as [Required]

public partial class Unit
{
    [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2214:DoNotCallOverridableMethodsInConstructors")]
    public Unit()
    {
        this.Staffs = new HashSet<Staff>();
    }

    public int UnitID { get; set; }
    public string UnitCode { get; set; }
    public string UnitName { get; set; }
    public string GroupName { get; set; }
    public byte IncentiveUnit { get; set; }
    public bool CallCenter { get; set; }
    public bool CDWUnit { get; set; }
    public string CDWSite { get; set; }
    public Nullable<int> SiteID { get; set; }
    public Nullable<int> DivisionID { get; set; }
    public bool WFCUnit { get; set; }
    public bool QAMonitored { get; set; }
    public bool NICEMonitored { get; set; }
    public string ListPrefix { get; set; }
    public string TSHSource { get; set; }
    public string StatsSource { get; set; }
    public string DialerSource { get; set; }
    public Nullable<int> CostCenterID { get; set; }
    public int WaterfallView { get; set; }
    public bool Locked { get; set; }
    public string Platform { get; set; }
    public Nullable<int> Supplier { get; set; }
    public string Work_Type { get; set; }

    [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")]
    public virtual ICollection<Staff> Staffs { get; set; }
}

Update


I tried going off @Izzy example. I feel like i'm closer, but the [Required] still doesn't seem to trigger a validation error when I submit a form without populating that field. @Izzy, is there something I might be missing?

View Model

public class UnitAdminViewModel
{
    public Unit Unit { get; set; }
    public List<Site> Site { get; set; }
    public IEnumerable<Work_Type> Work_Type { get; set; }

}

UnitMetaData class

[MetadataType(typeof(UnitMetaData))]
    public partial class Unit
    {

    }

    public class UnitMetaData {
        [Required(ErrorMessage = "Group is required")]
        public string GroupName { get; set; }

        [Required(ErrorMessage = "UnitName is required")]
        public string UnitName { get; set; }

        public string CDWSite { get; set; }

        public string Platform { get; set; }

        public Nullable<int> Supplier { get; set; }

        public string Work_Type { get; set; }
}

VIEW

    @model WebReportingToolDAL.Models.ViewModels.UnitAdminViewModel

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

<h2>Create</h2>

@using (Html.BeginForm()) 
{
    @Html.AntiForgeryToken()

<div class="form-horizontal">
    <h4>Unit</h4>
    <hr />
    @Html.ValidationSummary(true, "", new { @class = "text-danger" })

    <div class="form-group">
        @Html.LabelFor(model => model.Unit.UnitName, htmlAttributes: new { @class = "control-label col-md-2" })
        <div class="col-md-10">
            @Html.EditorFor(model => model.Unit.UnitName, new { htmlAttributes = new { @class = "form-control" } })
            @Html.ValidationMessageFor(model => model.Unit.UnitName, "", new { @class = "text-danger" })
        </div>
    </div>

    <div class="form-group">
        @Html.LabelFor(model => model.Unit.GroupName, htmlAttributes: new { @class = "control-label col-md-2" })
        <div class="col-md-10">
            @Html.EditorFor(model => model.Unit.GroupName, new { htmlAttributes = new { @class = "form-control" } })
            @Html.ValidationMessageFor(model => model.Unit.GroupName, "", new { @class = "text-danger" })
        </div>
    </div>

    <div class="form-group">
        @Html.LabelFor(model => model.Unit.CDWSite, htmlAttributes: new { @class = "control-label col-md-2" })
        <div class="col-md-10">
            @Html.DropDownListFor(model => model.Unit.CDWSite, new SelectList(Model.Site, "SiteName", "SiteName"), new { @class = "form-control" })
        </div>
    </div>

    <div class="form-group">
        @Html.LabelFor(model => model.Unit.Platform, htmlAttributes: new { @class = "control-label col-md-2" })
        <div class="col-md-10">
            @Html.DropDownListFor(model => model.Unit.Platform, new List<SelectListItem> { new SelectListItem { Text = "PSCC", Value = "PSCC" }, new SelectListItem { Text = "RC", Value = "RC" } }, new { @class = "form-control" }) 
        </div>
    </div>

    <div class="form-group">
        @Html.LabelFor(model => model.Unit.Supplier, htmlAttributes: new { @class = "control-label col-md-2" })
        <div class="col-md-10">
            @Html.DropDownListFor(model => model.Unit.Supplier, new List<SelectListItem> { new SelectListItem { Text = "0", Value = "0" }, new SelectListItem { Text = "1", Value = "1" } }, new { @class = "form-control" }) 
        </div>
    </div>

    <div class="form-group">
        @Html.LabelFor(model => model.Unit.Work_Type, htmlAttributes: new { @class = "control-label col-md-2" })
        <div class="col-md-10">
            @Html.DropDownListFor(model => model.Unit.Work_Type,new SelectList(Model.Work_Type, "Name", "Name"),new { @class = "form-control" })
        </div>
    </div>

    <div class="form-group">
        <div class="col-md-offset-2 col-md-10">
            <input type="submit" value="Create" class="btn btn-default" />
        </div>
    </div>
</div>
}

Controller

[HttpPost]
    [ValidateAntiForgeryToken]
    public ActionResult Create([Bind(Include = "UnitID,UnitCode,UnitName,GroupName,IncentiveUnit,CallCenter,CDWUnit,CDWSite,SiteID,DivisionID,WFCUnit,QAMonitored,NICEMonitored,ListPrefix,TSHSource,StatsSource,DialerSource,CostCenterID,WaterfallView,Locked,Platform,Supplier,Work_Type")] Unit unit)
    {
        if (ModelState.IsValid)
        {
            unit.UnitCode = "XX";
            unit.IncentiveUnit = 1;
            unit.CallCenter = true;
            unit.CDWUnit = true;
            unit.DivisionID = 2;
            unit.WFCUnit = false;
            unit.QAMonitored = false;
            unit.NICEMonitored = true;
            unit.ListPrefix = null;
            unit.TSHSource = null;
            unit.StatsSource = null;
            unit.DialerSource = null;
            unit.CostCenterID = 3;
            unit.WaterfallView = 1;
            unit.Locked = false;

            var siteId = (from s in db.Sites
                         where s.SiteName.ToLower().Equals(unit.CDWSite.ToLower())
                         select s.SiteID).First();

            unit.SiteID = siteId;

            db.Units.Add(unit);
            db.SaveChanges();
            return RedirectToAction("Index");
        }

        return View(unit);
    }

Upvotes: 0

Views: 3410

Answers (2)

Chris Pratt
Chris Pratt

Reputation: 239290

You can use a real view model, for one. Simply wrapping a bunch of entities in a class is missing the point of what view models are for. Your view models should only contain the properties that should be displayed/edited and it should hold the business logic for your view, such as the fact that GroupName is required (when it apparently isn't at the database level).

That means creating something like:

public class UnitViewModel
{
    // other properties you want to edit

    [Required]
    public string GroupName { get; set; }
}

Then, you use this rather than Unit in your view, and map the posted properties from UnitViewModel onto your Unit instance.

Upvotes: 2

Izzy
Izzy

Reputation: 6866

When using Database first approach you'll realise that the class is marked as partial So what you can do is make use of MetadataType attribute to achieve what you're after.

So go ahead and create a file and name it e.g. UnitMetaData. Your code should look something like:

public class UnitMetaData
{
    [Required(ErrorMessage = "Group is required")]
    public string GroupName { get; set; }
    //more properties
}

Your Unit class is partial so you can create it another file and use MetadataType as:

[MetadataType(typeof(UnitMetaData))]
public partial class Unit
{
}

More about MetadataType here

partial definition:

It is possible to split the definition of a class or a struct, an interface or a method over two or more source files. Each source file contains a section of the type or method definition, and all parts are combined when the application is compiled.

source

Please Note: Ensure the namespace is same as the generated Unit class, otherwise it will not work

Upvotes: 4

Related Questions