Polymorphic model binding / Complex Models - asp.net-mvc

I have problem in model binding. When I submit form it returns me id=0 and device is null? and how to solve it. My goal is to add new device, and choose device type from view by selector. if user selects smartphone it has to add fields for smartphone. I don't want to save device type in base class as Kind variable. Thanks in advance(sorry for english)
controller->
public IActionResult Index()
{
MainCont mainCont = new MainCont();
return View(mainCont);
}
index.cshtml ->
#model MainCont
#{
ViewData["Title"] = "Home Page";
}
<form action="home/create" method="post">
#Html.Partial("example",Model.Device)
<button type="submit">გაგზავნა</button>
</form>
example.cshtml ->
#model SmartPhone
#Html.TextBoxFor(model => model.imei)
#Html.TextBoxFor(model => model.screensize)
Device Model ->
public abstract class Device : Object
{
}
LaptopModel ->
public class Laptop : Device
{
public string CPU { get; set; }
public string GPu { get; set; }
}
MainCont ->
public class MainCont
{
public int Id{ get; set; }
public Device Device { get; set; }
}
SmartphoneModel ->
public class SmartPhone : Device
{
public string screensize { get; set; }
public string imei { get; set; }
}
model binder ->
using Bind.Models;
using Microsoft.AspNetCore.Mvc.ModelBinding;
using Microsoft.AspNetCore.Mvc.ModelBinding.Validation;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace Bind
{
public class DeviceModelBinder : IModelBinder
{
private Dictionary<Type, (ModelMetadata, IModelBinder)> binders;
public DeviceModelBinder(Dictionary<Type, (ModelMetadata, IModelBinder)> binders)
{
this.binders = binders;
}
public async Task BindModelAsync(ModelBindingContext bindingContext)
{
IModelBinder modelBinder;
ModelMetadata modelMetadata;
if (bindingContext.ModelType == typeof(Laptop))
{
(modelMetadata, modelBinder) = binders[typeof(Laptop)];
}
else if (bindingContext.ModelType == typeof(SmartPhone))
{
(modelMetadata, modelBinder) = binders[typeof(SmartPhone)];
}
else
{
bindingContext.Result = ModelBindingResult.Failed();
return;
}
var newBindingContext = DefaultModelBindingContext.CreateBindingContext(
bindingContext.ActionContext,
bindingContext.ValueProvider,
modelMetadata,
bindingInfo: null,
bindingContext.ModelName);
await modelBinder.BindModelAsync(newBindingContext);
bindingContext.Result = newBindingContext.Result;
if (newBindingContext.Result.IsModelSet)
{
// Setting the ValidationState ensures properties on derived types are correctly
bindingContext.ValidationState[newBindingContext.Result] = new ValidationStateEntry
{
Metadata = modelMetadata,
};
}
}
}
}
binderprovider ->
using Bind.Models;
using Microsoft.AspNetCore.Mvc.ModelBinding;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace Bind
{
public class DeviceModelBinderProvider: IModelBinderProvider
{
public IModelBinder GetBinder(ModelBinderProviderContext context)
{
if (context.Metadata.ModelType != typeof(Device))
{
return null;
}
var subclasses = new[] { typeof(Laptop), typeof(SmartPhone), };
var binders = new Dictionary<Type, (ModelMetadata, IModelBinder)>();
foreach (var type in subclasses)
{
var modelMetadata = context.MetadataProvider.GetMetadataForType(type);
binders[type] = (modelMetadata, context.CreateBinder(modelMetadata));
}
return new DeviceModelBinder(binders);
}
}
}

Here is a demo:
Index.cshtml(when select SmartPhone,use example.cshtml,when select Laptop,use example1.cshtml):
#model MainCont
#{
ViewData["Title"] = "Home Page";
}
<form asp-action="create" asp-controller="home" method="post">
<select id="select" name="select">
<option value="SmartPhone">SmartPhone </option>
<option value="Laptop">Laptop </option>
</select>
<div id="sample"></div>
<button type="submit">გაგზავნა</button>
</form>
#section scripts{
<script>
$(function () {
GetPartialView();
})
$("#select").change(function () {
GetPartialView();
})
function GetPartialView() {
$.ajax({
url: "/Test1/ReturnExample",
type: "POST",
data: {
select: $("#select").val()
},
success: function (data) {
$('#sample').html(data);
},
error: function (reponse) {
alert("error : " + reponse);
}
});
}
</script>
}
example.cshtml:
#model SmartPhone
#Html.TextBoxFor(model => model.imei)
#Html.TextBoxFor(model => model.screensize)
example1.cshtml:
#model Laptop
#Html.TextBoxFor(model => model.CPU)
#Html.TextBoxFor(model => model.GPu)
Controller:
public IActionResult Index()
{
return View(new MainCont());
}
public IActionResult ReturnExample(string select)
{
if (select == "SmartPhone")
{
return PartialView("~/Views/Test1/example.cshtml", new SmartPhone());
}
else {
return PartialView("~/Views/Test1/example1.cshtml", new Laptop());
}
}
Create Action in Home Controller:
[HttpPost]
public IActionResult Create([ModelBinder(typeof(DataBinder))]MainCont mainCont) {
return Ok();
}
DataBinder:
public class DataBinder : IModelBinder
{
public Task BindModelAsync(ModelBindingContext bindingContext)
{
if (bindingContext == null)
{
throw new ArgumentNullException(nameof(bindingContext));
}
var model1 = new MainCont();
var select = bindingContext.ValueProvider.GetValue("select").FirstValue;
if (select == "SmartPhone")
{
var model2 = new SmartPhone();
model2.screensize = bindingContext.ValueProvider.GetValue("screensize").FirstValue;
model2.imei = bindingContext.ValueProvider.GetValue("imei").FirstValue;
model1.Device = model2;
}
else if (select == "Laptop")
{
var model2 = new Laptop();
model2.CPU = bindingContext.ValueProvider.GetValue("CPU").FirstValue;
model2.GPu = bindingContext.ValueProvider.GetValue("GPu").FirstValue;
model1.Device = model2;
}
bindingContext.Result = ModelBindingResult.Success(model1);
return Task.CompletedTask;
}
}
result:

Related

Custom Validation for nested model in .net core

I am trying to validate a nested model using custom validation. But the problem is AttributeAdapterBase.AddValidation function is never called on nested model. However it works well with simple class property
Custom required validation attribute:
public interface IId
{
long Id { get; set; }
}
public class Select2RequiredAttribute : RequiredAttribute
{
public Select2RequiredAttribute(string errorMessage = "") : base()
{
ErrorMessage = errorMessage;
}
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
Type t = value.GetType();
if (typeof(IId).IsAssignableFrom(t))
{
if ((value as IId).Id == 0)
{
return new ValidationResult(ErrorMessage);
}
}
else
{
return new ValidationResult(ErrorMessage);
}
return ValidationResult.Success;
}
}
Attribute adapter base:
public class Select2RequiredAttributeAdapter : AttributeAdapterBase<Select2RequiredAttribute>
{
public Select2RequiredAttributeAdapter(Select2RequiredAttribute attribute, IStringLocalizer stringLocalizer) : base(attribute, stringLocalizer)
{
}
public override void AddValidation(ClientModelValidationContext context)
{
MergeAttribute(context.Attributes, "data-val", "true");
MergeAttribute(context.Attributes, "data-val-select2-required", GetErrorMessage(context));
}
public override string GetErrorMessage(ModelValidationContextBase validationContext)
{
return Attribute.ErrorMessage ?? GetErrorMessage(validationContext.ModelMetadata, validationContext.ModelMetadata.GetDisplayName());
}
}
Adapter provider:
public class Select2RequiredAdapterProvider : IValidationAttributeAdapterProvider
{
private readonly IValidationAttributeAdapterProvider _baseProvider = new ValidationAttributeAdapterProvider();
public IAttributeAdapter GetAttributeAdapter(ValidationAttribute attribute, IStringLocalizer stringLocalizer)
{
if (attribute is Select2RequiredAttribute)
{
return new Select2RequiredAttributeAdapter(attribute as Select2RequiredAttribute, stringLocalizer);
}
else
{
return _baseProvider.GetAttributeAdapter(attribute, stringLocalizer);
}
}
}
Startup.cs:
services.AddSingleton<IValidationAttributeAdapterProvider, Select2RequiredAdapterProvider>();
Model classes:
public interface IBaseBriefViewModel : IId
{
string Name { get; set; }
}
public class BaseBriefViewModel : IBaseBriefViewModel
{
public virtual long Id { get; set; }
public string Name { get; set; }
}
public class UserViewModel
{
public long Id { get; set; }
public string Name { get; set; }
[Select2Required("Branch is required.")]
public BaseBriefViewModel Branch { get; set; }
}
Branch select 2 partial view:
#model DataLibrary.ViewModels.BriefViewModels.BaseBriefViewModel
#{
var elementId = ViewData["ElementId"] != null && !string.IsNullOrEmpty(ViewData["ElementId"].ToString()) ? ViewData["ElementId"].ToString() : "branch-id";
}
<div class="form-group">
<label>Branch: <span class="text-danger"></span></label>
<div class="row">
<div class="#select2Class">
#Html.DropDownListFor(model => model.Id, new List<SelectListItem>() {
new SelectListItem()
{
Value = (Model!=null&&Model.Id>0)?Model.Id.ToString():"",
Text = (Model!=null&&Model.Id>0)?Model.Name:"",
Selected = (Model!=null&&Model.Id>0)?true:false,
}}, new { #id = elementId, #class = "form-control disable-field"})
#Html.ValidationMessageFor(model => model.Id, "", new { #class = "text-danger" })
</div>
</div>
</div>
<script>
$(function () {
var id = "#" + "#elementId";
var url = '/Branch/GetBranchsForSelect2';
var dataArray = function (params) {
params.page = params.page || 1;
return {
prefix: params.term,
pageSize: pageSize,
pageNumber: params.page,
};
};
Select2AutoCompleteAjax(id, url, dataArray, pageSize, "---Branch---");
});
</script>
All this code works well for server side. But for better user experience I want to show error before submitting form. How can I achieve this? I want to use this BaseBriefViewModel for a lot of Select2 in the project. So hard coding a static error message is not a good idea. What I really want to do is pass a error message from parent object. Like Branch is required in this specific case. Maybe in some other class I might pass Product is required
Any direction will be appreciated
At the moment this is not supported - but support is in planned. See dotnet github issue:
https://github.com/dotnet/runtime/issues/36093

model binding asp.net core web api

Im trying to bind model, but when i bind smartphone or laptop props, im losing myContnet main prop id, here is mainCont:
`namespace Bind.Models
{
public class MainCont
{
public int Id{ get; set; }
public Device Device { get; set; }
}
}`
using Bind.Models;
using Microsoft.AspNetCore.Mvc.ModelBinding;
using Microsoft.AspNetCore.Mvc.ModelBinding.Validation;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace Bind
{
public class DeviceModelBinder : IModelBinder
{
public Task BindModelAsync(ModelBindingContext bindingContext)
{
if (bindingContext == null)
{
throw new ArgumentNullException(nameof(bindingContext));
}
var model1 = new MainCont();
var select = bindingContext.ValueProvider.GetValue("select").FirstValue;
if (select == "SmartPhone")
{
var model2 = new SmartPhone();
model2.screensize = bindingContext.ValueProvider.GetValue("screensize").FirstValue;
model2.imei = bindingContext.ValueProvider.GetValue("imei").FirstValue;
model1.Device = model2;
}
else if (select == "Laptop")
{
var model2 = new Laptop();
model2.CPU = bindingContext.ValueProvider.GetValue("CPU").FirstValue;
model2.GPu = bindingContext.ValueProvider.GetValue("GPu").FirstValue;
model1.Device = model2;
}
bindingContext.Result = ModelBindingResult.Success(model1);
return Task.CompletedTask;
}
}
}
i cant set id, of course in DeviceBinder i wrote new mainCont(), and how can i solve it? Sorry for bad english ;)
Firstly,you need to add a input in your view,so that you can pass the Id when form post.
Here is a demo:
Index.cshtml(I use a hidden input,if you want to edit the Id in the view,you can remove hidden):
<form asp-action="create" asp-controller="home" method="post">
<input asp-for="Id" hidden>
<select id="select" name="select">
<option value="SmartPhone">SmartPhone </option>
<option value="Laptop">Laptop </option>
</select>
<div id="sample"></div>
<button type="submit">გაგზავნა</button>
</form>
And then,in your CustomModelBinder,bind it to model:
public Task BindModelAsync(ModelBindingContext bindingContext)
{
if (bindingContext == null)
{
throw new ArgumentNullException(nameof(bindingContext));
}
var model1 = new MainCont();
//bind the Id here
model1.Id= Convert.ToInt32(bindingContext.ValueProvider.GetValue("Id").FirstValue);
var select = bindingContext.ValueProvider.GetValue("select").FirstValue;
if (select == "SmartPhone")
{
var model2 = new SmartPhone();
model2.screensize = bindingContext.ValueProvider.GetValue("screensize").FirstValue;
model2.imei = bindingContext.ValueProvider.GetValue("imei").FirstValue;
model1.Device = model2;
}
else if (select == "Laptop")
{
var model2 = new Laptop();
model2.CPU = bindingContext.ValueProvider.GetValue("CPU").FirstValue;
model2.GPu = bindingContext.ValueProvider.GetValue("GPu").FirstValue;
model1.Device = model2;
}
bindingContext.Result = ModelBindingResult.Success(model1);
return Task.CompletedTask;
}
Controller(When create view,I give the Id to the view):
public IActionResult Index()
{
return View(new MainCont { Id=1});
}
result:

Second DDL not firing

good day, why my second DDL do not fire when I select the Parent dropdown list.
Also can anyone elaborate the script and the [ 0] in the View beside m=>m.StateModel
And for the last question, how can I make this DDL dynamic, How can I populate it using database data?
Model
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
namespace Kaskid.Models
{
public class State
{
public int Id { get; set; }
public string StateName { get; set; }
}
public class City
{
public int Id { get; set; }
public int StateId { get; set; }
public string CityName { get; set; }
}
public class CountryModel
{
public List<State> StateModel { get; set; }
public SelectList FilteredCity { get; set; }
}
}
Controller
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using Kaskid.Models;
namespace Kaskid.Controllers
{
public class HomeController : Controller
{
//
// GET: /Home/
public ActionResult Index()
{
CountryModel objCountryModel = new CountryModel();
objCountryModel.StateModel = new List<State>();
objCountryModel.StateModel = GetAllState();
return View(objCountryModel);
}
[HttpPost]
public ActionResult GetCityByStateId(int stateId)
{
List<City> objCity = new List<City>();
objCity = GetAllcity().Where(m => m.StateId == stateId).ToList();
SelectList obgcity = new SelectList(objCity, "Id", "CityName", 0);
return Json(obgcity);
}
public List<State> GetAllState()
{
List<State> objState = new List<State>();
objState.Add(new State {Id = 0,StateName="Select State"});
objState.Add(new State {Id = 1,StateName = "State 1"});
objState.Add(new State {Id = 2, StateName="State 2"});
objState.Add(new State {Id = 3, StateName="State 3"});
return objState;
}
public List<City> GetAllcity()
{
List<City> objCity = new List<City>();
objCity.Add(new City{Id = 1,StateId = 1, CityName = "City1-1" });
objCity.Add(new City{Id = 2,StateId=2,CityName = "City2-1"});
objCity.Add(new City{Id = 3,StateId=4,CityName="City4-1"});
objCity.Add(new City{Id = 4,StateId=1,CityName="City1-2"});
return objCity;
}
}
}
View
#model Kaskid.Models.CountryModel
#{
ViewBag.Title = "Index";
}
<script language="javascript">
function GetCity(_stateId) {
var procemessage = "<option value='0'> Please wait...</option>";
$('#ddlcity').html(procemessage).show();
var url = "/Home/GetCityByStaeId/";
$.ajax({
url: url,
data: { stateid: _stateId },
cache: false,
type: "POST",
success: function (data) {
var markup = "<option value='0'>Select City</option>";
for (var x = 0; x < data.length; x++) {
markup += "<option value=" + data[x].Value + ">" + data[x].Text + "</option>";
}
$('#ddlcity').html(markup).show();
},
error: function (reponse) {
alert("error : " + reponse);
}
});
}
</script>
<h4>
MVC3 Cascading Dropdown List Using Jquery</h4>
#using (Html.BeginForm("", ""))
{
#Html.DropDownListFor(m => m.StateModel[0].Id, new SelectList(Model.StateModel, "Id", "StateName"), new { #id = "ddlstate", #style = "width:200px;", #onchange = "javascript:GetCity(this.value);" })
<br />
<br />
<select id="ddlcity" name="ddlcity" style="width: 200px">
</select>
}
You've got a typo:
var url = "/Home/GetCityByStaeId/";
But your controller action is called GetCityByStateId and not GetCityByStaeId. Also you may take a look at a similar example I once wrote where you could take some ideas about improving your code.

Client side validation for my dropdown populated with enum values

I have a view where I use a dropdown list with enum:
public enum MaterialWorthEnumViewModel
{
[Display(Name = "")] Undefined,
[Display(Name = "< 1.000€")] LessThan1000,
[Display(Name = "1.000€ < 10.000€")] Between1000And10000,
[Display(Name = "10.000€ < 100.000€")] Between10000And100000,
[Display(Name = "100.000€ < 25.000.000€")] Between100000And25000000,
[Display(Name = "> 25.000.000€")] GreaterThan250000000,
}
I use a view model with this view:
public class MaterialEditNewViewModel
{
public int RequestID { get; set; }
...
[EnumRequired]
public MaterialWorthEnumViewModel MaterialWorth { get; set; }
}
As you can see above, I used a custom validation [EnumRequired] I grab the code from a blog online.
[AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Field | AttributeTargets.Property, AllowMultiple = false)]
public class EnumRequiredAttribute : RequiredAttribute
{
private const string UNDEFINED_VALUE = "Undefined";
public string UndefinedValue { get; set; }
public EnumRequiredAttribute() : this(UNDEFINED_VALUE)
{ }
public EnumRequiredAttribute(string undefinedValue) : base()
{
if (String.IsNullOrWhiteSpace(undefinedValue))
{
throw new ArgumentNullException("undefinedValue");
}
UndefinedValue = undefinedValue;
}
public override bool IsValid(object value)
{
if (value == null)
{
return false;
}
var undefined = Enum.Parse(value.GetType(), UndefinedValue);
return !Enum.Equals(value, undefined);
}
}
Below is for the client side validation
public class ModelClientValidationEnumRequiredRule : ModelClientValidationRule
{
public ModelClientValidationEnumRequiredRule(string errorMessage, string undefinedValue)
{
base.ErrorMessage = errorMessage;
base.ValidationType = "enumrequired";
base.ValidationParameters.Add("undefinedvalue", undefinedValue);
}
}
public class EnumRequiredAttributeAdapter : DataAnnotationsModelValidator<EnumRequiredAttribute>
{
public EnumRequiredAttributeAdapter(ModelMetadata metadata, ControllerContext context, EnumRequiredAttribute attribute)
: base(metadata, context, attribute)
{ }
public override IEnumerable<ModelClientValidationRule> GetClientValidationRules()
{
return new ModelClientValidationEnumRequiredRule[]
{
new ModelClientValidationEnumRequiredRule(base.ErrorMessage, Attribute.UndefinedValue)
};
}
}
Below is the javascript for the client side validation
Sys.Mvc.ValidatorRegistry.validators.enumrequired = function (rule) {
var undefinedValue = rule.ValidationParameters.undefinedvalue;
return function (value, context) {
return value != undefinedValue;
}
}
I also updated my GLobal.asax file:
DataAnnotationsModelValidatorProvider.RegisterAdapter(typeof(EnumRequiredAttribute), typeof(EnumRequiredAttributeAdapter));
The validation works pretty well on the server side but the client side validation is never triggered. So when I didn't choose any value on my view for my dropdown enum, I reach the action in the controller and then the server side validation occured and I go back to the view. I concluded that the client side validation didn't occurred.
Does someone can help me doing valid client side validation for this dropdown enum ?
Thanks. I'm a bit lost.
I don't see any relationship between your EnumRequiredAttribute and the other 2 classes. If you are using ASP.NET MVC 3 you need to associate your custom validation attribute with the adapter. This could be done in Application_Start:
DataAnnotationsModelValidatorProvider.RegisterAdapter(
typeof(EnumRequiredAttribute),
typeof(EnumRequiredAttributeAdapter)
);
Also on your client side you have shown some js code that relies on Microsoft*.js libraries. Those are now obsolete and should no longer be used. The default standard in ASP.NET MVC 3 for client side validation is the jquery.validate plugin.
So let's take an example.
Model:
public class MyViewModel
{
[EnumRequired]
public MaterialWorthEnumViewModel MaterialWorth { get; set; }
}
Controller:
public class HomeController : Controller
{
public ActionResult Index()
{
return View(new MyViewModel());
}
[HttpPost]
public ActionResult Index(MyViewModel model)
{
return View(model);
}
}
View (Index.cshtml):
#model MyViewModel
<script src="#Url.Content("~/Scripts/jquery.validate.js")" type="text/javascript"></script>
<script src="#Url.Content("~/Scripts/jquery.validate.unobtrusive.js")" type="text/javascript"></script>
<script src="#Url.Content("~/Scripts/enumrequiredadapter.js")" type="text/javascript"></script>
#using (Html.BeginForm())
{
#Html.LabelFor(x => x.MaterialWorth)
#Html.EditorFor(x => x.MaterialWorth)
#Html.ValidationMessageFor(x => x.MaterialWorth)
<button type="submit">OK</button>
}
and finally the enumrequiredadapter.js adapter:
(function ($) {
$.validator.unobtrusive.adapters.add('enumrequired', ['undefinedvalue'], function (options) {
options.rules['enumrequired'] = options.params;
if (options.message != null) {
options.messages['enumrequired'] = options.message;
}
});
$.validator.addMethod('enumrequired', function (value, element, params) {
return value != params.undefinedvalue;
});
})(jQuery);
Also don't forget to remove all traces of Microsoft*.js script references from your site. And that's pretty much it.

Paperclip for asp.net mvc

Is it exists kind of plugin like Paperclip for Rails?
it is really painful to implement own system for uploading files the resize it...
will be cool to have Attribute for model that will get params like so:
Model:
[Paperclip(Sizes={thumb="100x20",big="200x40"},Path="~/public/")]
public string Image{get;set;}
View:
Html.Editor(x=>x.Image)
here is small tutorial for rails.
Looks like this question is pretty old, I've stumbled across it by accident and because it doesn't seem to be answered yet, I decided to bring my 2¢.
I don't know if such plugin exists for ASP.NET but it would be trivially easy to write one:
[AttributeUsage(AttributeTargets.Property)]
public class PaperClipAttribute : Attribute, IMetadataAware
{
public PaperClipAttribute(string uploadPath, params string[] sizes)
{
if (string.IsNullOrEmpty(uploadPath))
{
throw new ArgumentException("Please specify an upload path");
}
UploadPath = uploadPath;
Sizes = sizes;
}
public string UploadPath { get; private set; }
public string[] Sizes { get; private set; }
public void OnMetadataCreated(ModelMetadata metadata)
{
metadata.AdditionalValues["PaperClip"] = this;
}
}
then define a custom model binder for the HttpPostedFileBase type:
public class PaperClipModelBinder : DefaultModelBinder
{
public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
var paperClip = bindingContext.ModelMetadata.AdditionalValues["PaperClip"] as PaperClipAttribute;
if (paperClip == null)
{
return base.BindModel(controllerContext, bindingContext);
}
var uploadedFile = base.BindModel(controllerContext, bindingContext) as HttpPostedFileBase;
if (uploadedFile == null)
{
return null;
}
var uploadPath = controllerContext.HttpContext.Server.MapPath(paperClip.UploadPath);
if (!Directory.Exists(uploadPath))
{
throw new ArgumentException(string.Format("The specified folder \"{0}\" does not exist", uploadPath));
}
var sizes =
(from size in paperClip.Sizes
let tokens = size.Split('x')
select new Size(int.Parse(tokens[0]), int.Parse(tokens[1]))
).ToArray();
foreach (var size in sizes)
{
var extension = Path.GetExtension(uploadedFile.FileName);
var outputFilename = Path.Combine(
uploadPath,
Path.ChangeExtension(
string.Format("image{0}x{1}", size.Width, size.Height),
extension
)
);
Resize(uploadedFile.InputStream, outputFilename, size);
}
return base.BindModel(controllerContext, bindingContext);
}
private void Resize(Stream input, string outputFile, Size size)
{
using (var image = Image.FromStream(input))
using (var bmp = new Bitmap(size.Width, size.Height))
using (var gr = Graphics.FromImage(bmp))
{
gr.CompositingQuality = CompositingQuality.HighSpeed;
gr.SmoothingMode = SmoothingMode.HighSpeed;
gr.InterpolationMode = InterpolationMode.HighQualityBicubic;
gr.DrawImage(image, new Rectangle(0, 0, size.Width, size.Height));
bmp.Save(outputFile);
}
}
}
which will be registered in Application_Start:
protected void Application_Start()
{
AreaRegistration.RegisterAllAreas();
RegisterGlobalFilters(GlobalFilters.Filters);
RegisterRoutes(RouteTable.Routes);
ModelBinders.Binders[typeof(HttpPostedFileBase)] = new PaperClipModelBinder();
}
and we are pretty much done. All that's left is classic stuff.
View model:
public class MyViewModel
{
[PaperClip("~/App_Data", "100x20", "200x40", "640x480")]
[Required(ErrorMessage = "Please select a file to upload")]
public HttpPostedFileBase Image { get; set; }
}
Controller:
public class HomeController : Controller
{
public ActionResult Index()
{
return View(new MyViewModel());
}
[HttpPost]
public ActionResult Index(MyViewModel model)
{
if (!ModelState.IsValid)
{
return View(model);
}
return Content("thanks for uploading");
}
}
View:
#model MyViewModel
#using (Html.BeginForm(null, null, FormMethod.Post, new { enctype = "multipart/form-data" }))
{
#Html.LabelFor(x => x.Image)
<input type="file" name="image" />
#Html.ValidationMessageFor(x => x.Image)
<input type="submit" value="Upload" />
}

Resources