How to restrict the file types in FileUpload in MVC3? - asp.net-mvc-3

I have a fileupload function where users can upload files. I want to restrict the users from upload certain file types. The types allowed are: .doc,.xlsx,.txt,.jpeg.
How I can do this?
This is my actual file upload code:
public ActionResult UploadFile(string AttachmentName, BugModel model)
{
BugModel bug = null;
if (Session["CaptureData"] == null)
{
bug = model;
}
else
{
bug = (BugModel)Session["CaptureData"];
}
foreach (string inputTagName in Request.Files)
{
HttpPostedFileBase file1 = Request.Files[inputTagName];
if (file1.ContentLength > 0)
{
string path = "/Content/UploadedFiles/" + Path.GetFileName(file1.FileName);
string savedFileName = Path.Combine(Server.MapPath("~" + path));
file1.SaveAs(savedFileName);
BugAttachment attachment = new BugAttachment();
attachment.FileName = "~" + path.ToString();
attachment.AttachmentName = AttachmentName;
attachment.AttachmentUrl = attachment.FileName;
bug.ListFile.Add(attachment);
model = bug;
Session["CaptureData"] = model;
}
}
ModelState.Clear();
return View("LoadBug", bug);
}

The first thing to verify is whether the file extension contained in file1.FileName matches one of the allowed extensions. Then if you really want to ensure that the user hasn't renamed some other file type to an allowed extension you will need to look into the contents of the file to recognize whether it is one of the allowed types.
Here's an example how to check whether the file extension belongs to a list of predefined extensions:
var allowedExtensions = new[] { ".doc", ".xlsx", ".txt", ".jpeg" };
var extension = Path.GetExtension(file1.FileName);
if (!allowedExtensions.Contains(extension))
{
// Not allowed
}

[AttributeUsage(AttributeTargets.Property, AllowMultiple = false)]
public class AllowedFileExtensionAttribute : ValidationAttribute
{
public string[] AllowedFileExtensions { get; private set; }
public AllowedFileExtensionAttribute(params string[] allowedFileExtensions)
{
AllowedFileExtensions = allowedFileExtensions;
}
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
var file = value as HttpPostedFileBase;
if (file != null)
{
if (!AllowedFileExtensions.Any(item => file.FileName.EndsWith(item, StringComparison.OrdinalIgnoreCase)))
{
return new ValidationResult(string.Format("{1} için izin verilen dosya uzantıları : {0} : {2}", string.Join(", ", AllowedFileExtensions), validationContext.DisplayName, this.ErrorMessage));
}
}
return null;
}
}
Usage In Model
[AllowedFileExtension(".jpg", ".png", ".gif", ".jpeg")]
public HttpPostedFileBase KategoriResmi { get; set; }

You can use the ContentType property of the HttpPostedFileBase for a basic check of the file type (mime type): See MSDN's page on the Content-Type property here
Here is one way to do it:
private static bool IsValidContentType(string contentType)
{
string ct = contentType.ToLower();
return ((ct == "application/msword") || (ct == "application/pdf") || (ct == "application/vnd.openxmlformats-officedocument.wordprocessingml.document"));
}
etc..
However, for a deeper inspection, you will have to inspect the file content. It's easy to change a file extension..

Related

Does the IClientValidator support input file?

Edit
I found that the problem is that View Components are unable to have an #section (see ViewComponent and #Section #2910 ) so adding custom client-side validation using the unobtrusive library seems imposible (or very complex). Moreover, the inability of including the required javascript into a View Component makes me regret of following this approach to modularize my app in the first place...
I am learning to make custom validation attributes with client-side support. I was able to implement a custom validator for a string property and it works pretty well, but when I tried to make one for input file it doesn't work (i.e. when I select a file in my computer, the application doesn't display the validation messages. The server-side validation works. Here is some code that shows my implementation.
The class of the model
public class UploadPanelModel
{
public int? ID { get; set; }
public string Title { get; set; }
public string Description { get; set; } //Raw HTML with the panel description
[FileType(type: "application/pdf")]
[FileSize(maxSize: 5000000)]
public IFormFile File { get; set; }
public byte[] FileBytes { get; set; }
public ModalModel Modal { get; set; } //Only used if the Upload panel uses a modal.
The validator
public class FileSizeAttribute : ValidationAttribute, IClientModelValidator
{
private long _MaxSize { get; set; }
public FileSizeAttribute (long maxSize)
{
_MaxSize = maxSize;
}
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
UploadPanelModel panel = (UploadPanelModel)validationContext.ObjectInstance;
return (panel.File==null || panel.File.Length <= _MaxSize) ? ValidationResult.Success : new ValidationResult(GetFileSizeErrorMessage(_MaxSize));
}
private string GetFileSizeErrorMessage(long maxSize)
{
double megabytes = maxSize / 1000000.0;
return $"El archivo debe pesar menos de {megabytes}MB";
}
public void AddValidation(ClientModelValidationContext context)
{
if(context == null)
{
throw new ArgumentNullException(nameof(context));
}
MergeAttribute(context.Attributes, "data-val", "true");
MergeAttribute(context.Attributes, "data-val-filesize", GetFileSizeErrorMessage(_MaxSize));
var maxSize = _MaxSize.ToString();
MergeAttribute(context.Attributes, "data-val-filesize-maxsize", maxSize);
}
private bool MergeAttribute(IDictionary<string, string> attributes, string key, string value)
{
if (attributes.ContainsKey(key))
{
return false;
}
attributes.Add(key, value);
return true;
}
}
The javascript in the Razor View
#section Scripts{
#{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
<script type="text/javascript">
$.validator.addMethod('filesize',
function (value, element, params) {
var size = $((params[0]).val()).size(),
maxSize = params[1];
if (size < maxSize) {
return false;
}
else {
return false;
}
}
);
$.validator.unobtrusive.adapters.add('filesize',
['maxSize'],
function (options) {
var element = $(options.form).find('input#File')[0];
options.rules['filesize'] = [element, options.params['maxSize']];
options.messages['filesize'] = options.message;
}
);
</script>
I always return false in the javascript method to force the application to show the validation error regardless the chosen file, but it still doesn't work.
Your addMethod() function will be throwing an error because params[0] is not a jQuery object and has no .val() (you also have the $ in the wrong place). You would need to use
var size = params[0].files[0].size;
However I suggest you write you scripts as
$.validator.unobtrusive.adapters.add('filesize', ['maxsize'], function (options) {
options.rules['filesize'] = { maxsize: options.params.maxsize };
if (options.message) {
options.messages['filesize'] = options.message;
}
});
$.validator.addMethod("filesize", function (value, element, param) {
if (value === "") {
return true;
}
var maxsize = parseInt(param.maxsize);
if (element.files != undefined && element.files[0] != undefined && element.files[0].size != undefined) {
var filesize = parseInt(element.files[0].size);
return filesize <= maxsize ;
}
return true; // in case browser does not support HTML5 file API
});

How do you do File Upload method in AppServices for aspnetboilerplate?

I really like aspnetboilerplate framework, I learning/using it now..
How do you do 'File Upload' logic/method in AppServices for aspnetboilerplate? The angular part have I figured out and is working fine.
How is it intended to write the methods receiving a file upload in the appservice layer? There is good examples on crud, and swagger figures them out. Now I want to implement file upload. Is this even possible in the appservice layer, or do I have to do this in the controller methods?
There is no way to do that via the ApplicationServices. You need to do that in the Web project with an MVC controller and action, like you would do it in a regular project. If you need more help, I can assist.
We implement file upload method :
- use sql server fileTable technology
- implement application method's service to accept data similar to this Dto
public FileDto File { get; set; }
...
public class FileDto
{
public FileDto()
{
}
public FileDto(string file, string fileType)
{
File = file;
FileType = fileType;
}
[DisableAuditing]
public string File { get; set; }
public string FileName { get; set; }
public Guid? StreamId { get; set; }
public string FileType { get; set; } = "";
[DisableAuditing]
public string FileWithHeader
{
get
{
if (FileType == null|| FileType=="")
return "";
if (FileType.ToLower() == "jpg" || FileType.ToLower() == "jpeg")
return "data:image/Jpeg;base64," + File;
if (FileType.ToLower() == "png")
return "data:image/png;base64," + File;
if (FileType.ToLower() == "gif")
return "data:image/gif;base64," + File;
if (FileType.ToLower() == "ppt")
return "data:application/vnd.ms-powerpoint;base64," + File;
if (FileType.ToLower() == "xls")
return "data:application/vnd.ms-excel;base64," + File;
if (FileType.ToLower() == "doc")
return "data:application/msword;base64," + File;
if (FileType.ToLower() == "zip")
return "data:application/zip;base64," + File;
if (FileType.ToLower() == "exe")
return "data:application/octet-stream;base64," + File;
if (FileType.ToLower() == "txt")
return "data:text/plain;base64," + File;
if (FileType.ToLower() == "pdf")
return "data:application/pdf;base64," + File;
if (FileType.ToLower() == "bmp")
return "data:image/bmp;base64," + File;
if (FileType.ToLower() == "csv")
return "data:text/csv;base64," + File;
if (FileType.ToLower() == "pptx")
return "data:application/vnd.openxmlformats-officedocument.presentationml.presentation;base64," + File;
if (FileType.ToLower() == "xlsx")
return "data:application/vnd.openxmlformats-officedocument.spreadsheetml.sheet;base64," + File;
if (FileType.ToLower() == "docx")
return "data:application/vnd.openxmlformats-officedocument.wordprocessingml.document;base64," + File;
if (FileType.ToLower() == "rar")
return "data:application/x-rar-compressed;base64," + File;
if (FileType.ToLower() == "rtf")
return "data:application/rtf;base64," + File;
return "";
}
}
}
and method implementation
public UploadFileOutput UploadFile(UploadFileInput input)
{
if (input.Id <= 0 || input.File == null)
{
throw new UserFriendlyException(L("GetDataError"));
}
return new UploadFileOutput()
{
StreamId = _attachmentRepo.InsertAttachment(input.File.FileName, Convert.FromBase64String(input.File.File), LIMSConsts.HeightThumbnail, true, ServerConfig.ThumbnailImagePath),
};
}
insertAttachment method:
public Guid InsertAttachment(string fileName, byte[] fileStream, int heightThumbnail = 50, bool saveThumbnail = false,string thumbLocationPath=null)
{
Guid AttachmentId = Guid.Empty;
SqlParameter _attachmentId = new SqlParameter();
_attachmentId.ParameterName = "#attachmentId";
_attachmentId.Direction = System.Data.ParameterDirection.InputOutput;
_attachmentId.Value = AttachmentId;
SqlParameter _fileStream = new SqlParameter();
_fileStream.SqlDbType = System.Data.SqlDbType.VarBinary;
_fileStream.Value = fileStream;
_fileStream.ParameterName = "#fileStream";
Context.Database.ExecuteSqlCommand("EXECUTE dbo.sp_AttachmentFile_Insert #AttachmentId OUTPUT,#fileName,#fileStream",
_attachmentId,
new SqlParameter("#fileName", fileName),
_fileStream);
if (saveThumbnail == true) {
var ms = new MemoryStream(fileStream);
var image = Image.FromStream(ms);
var width = (int)(heightThumbnail * image.Width / image.Height);
var height = (int)(heightThumbnail);
var Thumbnail = new Bitmap(width, height);
Graphics.FromImage(Thumbnail).DrawImage(image, 0, 0, width, height);
Bitmap Thumb= new Bitmap(Thumbnail);
Thumb.Save(Path.Combine(thumbLocationPath, _attachmentId.Value+".jpg"), ImageFormat.Jpeg);
}
return (Guid)_attachmentId.Value;
}
and Stored procedure implementation
ALTER PROC [dbo].[sp_AttachmentFile_Insert]
#AttachmentId uniqueidentifier out,
#fileName nvarchar(256),
#fileStream VARBINARY(max)
AS
SET #AttachmentId=NEWID()
WHILE(EXISTS(SELECT 1 FROM dbo.Attachment WHERE name=#fileName))
BEGIN
DECLARE #fileExtention NVARCHAR(100)
SELECT #fileExtention ='.'+dbo.GetFileExtension(#fileName)
SET #fileName=REPLACE(#fileName,#fileExtention,'')+
CAST(DATEPART( DAY, GETDATE()) AS VARCHAR(10))+'_'
+CAST(DATEPART( HOUR, GETDATE()) AS VARCHAR(10))+'_'+
+CAST(DATEPART( MINUTE, GETDATE()) AS VARCHAR(10))+'_'+
+CAST(DATEPART( SECOND, GETDATE()) AS VARCHAR(10))+#fileExtention
END
INSERT into dbo.Attachment(stream_id,name,file_stream)
VALUES(#AttachmentId,#fileName,#fileStream)
and finally Thank to "Halil İbrahim Kalkan" for thihs awesome framework.
All application services have access to the HttpContext object, so they are also able to handle file upload. To upload a file to an app. service make sure to use the correct url + headers.
App. service example:
public class MyUploadService : MyApplicationBaseClass, IMyUploadService
{
/// <summary>
/// References the logging service.
/// </summary>
private readonly ILogger _logger;
/// <summary>
/// Construct an new instance of this application service.
/// </summary>
public MyUploadService(ILogger logger)
{
_logger = logger;
}
/// <summary>
/// Method used to handle HTTP posted files.
/// </summary>
public async Task Upload()
{
// Checking if files are sent to this app service.
if(HttpContext.Current.Request.Files.Count == 0)
throw new UserFriendlyException("No files given.");
// Processing each given file.
foreach(var file in HttpContext.Current.Request.Files)
{
// Reading out meta info.
var fileName = file.fileName;
var extension = Path.GetExtension(file.FileName);
// Storing file on disk.
file.SaveAs("~/uploads");
}
}
}
There is no way to upload a file from Appservice you need to create a web Api controler with a particular method for this action.
public class EntityImageController : AbpApiController
{
private IEntityImageAppService iEntityAppService;
public EntityImageController( IEntityImageAppService pEntityImgAppService ) : base()
{
this.LocalizationSourceName = AppConsts.LocalizationSourceName;
this.iEntityImgAppService = pEntityImgAppService;
}
[AbpAuthorize( PermissionNames.Entity_Update )]
[HttpPost]
public async Task<HttpResponseMessage> Set()
{
// Check if the request contains multipart/form-data.
if( !Request.Content.IsMimeMultipartContent() )
{
throw new HttpResponseException( HttpStatusCode.UnsupportedMediaType );
}
string root = HttpContext.Current.Server.MapPath( "~/App_Data" );
var provider = new MultipartFormDataStreamProvider( root );
try
{
// Read the form data.
await Request.Content.ReadAsMultipartAsync( provider );
var mEntityId = provider.FormData[ "EntityId" ];
MultipartFileData mFileData = provider.FileData.FirstOrDefault();
var mFileInfo = new FileInfo( mFileData.LocalFileName );
var mImageBytes = File.ReadAllBytes( mFileInfo.FullName );
await this.iEntityImgAppService.Set( new EntityImageInput
{
ImageInfo = mImageBytes,
EntityId = Convert.ToInt32( mEntityId )
} );
return Request.CreateResponse( HttpStatusCode.OK );
}
catch( System.Exception e )
{
return Request.CreateErrorResponse( HttpStatusCode.InternalServerError, e );
}
}
}
Create a file upload controller in the web.core project.
Then reference your appService to process the file.
[HttpPost]
[AbpMvcAuthorize(PermissionNames.TenantPageJob)]
public async Task UploadExcelJobs()
{
var files = Request.Form.Files;
foreach (var file in files)
{
if (file.Length > 0 && file.ContentType.Contains("excel"))
{
var targetPath = Path.Combine(Path.GetTempPath(), (new Guid().ToString()), file.FileName);
var fs = new FileStream(targetPath, FileMode.OpenOrCreate);
await file.CopyToAsync(fs);
await _myAppService.ProcessFile(targetPath);
System.IO.File.Delete(targetPath);
}
}
}
The point is is how to get the files from the method of the class XXXAppService which derived from ApplicationService, not from the XXXController which derived from AbpController or Microsoft.AspNetCore.Mvc.Controller.
So you should remember the class: HttpContext/HtttpRequest/HttpResponse!!!
Solution: Inject httpContextAccessor in the class XXXAppService will achive it.
https://learn.microsoft.com/en-us/aspnet/core/fundamentals/http-context?view=aspnetcore-5.0
Here is my codes, wish will help you!
------Server side (ABP)--------------
[Route("api/")]
public class XXXAppService : ApplicationService
{
private readonly IHttpContextAccessor _httpContextAccessor;
public XXXAppService(IHttpContextAccessor httpContextAccessor)
{
_httpContextAccessor = httpContextAccessor;
}
[HttpPost, Route("upload")]
public void UploadFile()
{
var files = _httpContextAccessor.HttpContext.Request.Form.Files;
//do logics as you like here...
}
}
---(1) UI (PrimeNG upload component)
<p-fileUpload name="demo[]" [multiple]="true" [url]="uploadUrl"
(onUpload)="onUpload($event)">
<ng-template pTemplate="content">
<ul *ngIf="uploadedFiles.length">
<li *ngFor="let file of uploadedFiles">{{file.name}} - {{file.size / 1000}}kb</li>
</ul>
</ng-template>
</p-fileUpload>
---(2) UI logic (component)
import { AppConsts } from '#shared/AppConsts';
uploadUrl: string = '';
uploadedFiles: any[] = [];
ngOnInit() {
//http://localhost:21021/api/upload
let url_ = AppConsts.remoteServiceBaseUrl + "/api/upload";
this.uploadUrl = url_.replace(/[?&]$/, "");
}
onUpload(event: any) {
_.forEach(event.files, v => {
this.uploadedFiles.push(v);
});
}

RadEditor.ImageManager not inserting image on clicking Insert

I have a radEditor control with the ImageManager enabled. this feature worked fine within our last version we had (2011 version) but now with the version we have, the image manager does not insert the image selected. Below is my radEditor html tag:
<telerik:RadEditor ID="txtRTE"
SpellCheckSettings-AllowAddCustom="false"
ToolsFile="~/SimpleRTEEditorTools.xml"
OnClientLoad="HandleRTEClientLoad"
ExternalDialogsPath="~/RadControls/EditorDialogs/"
runat="server"
Height="200"
Skin="Default"
EditModes="Design"
AllowScripts="false"
StripFormattingOptions="All">
<ImageManager ViewPaths=".." UploadPaths=".." SearchPatterns="*.jpg,*.gif,*.png" EnableImageEditor="False" ViewMode="Grid" />
I am editing the ViewPaths and uploadPaths within the code behind's page_load method:
txtRTE.ImageManager.ViewPaths = paths;
txtRTE.ImageManager.UploadPaths = paths;
txtRTE.ImageManager.ContentProviderTypeName = typeof(FolderContentProvider).AssemblyQualifiedName;
We implemented our own content provider as seen below:
public class FolderContentProvider : FileBrowserContentProvider
{
private string ROOT_DIRECTORY_FULL_PATH =
//Path to record documents folder
System.Configuration.ConfigurationManager.AppSettings[Constants.RECORD_DOC_ROOT_FOLDER_APP_KEY].ToString() +
//folder containing images
Constants.Record_DOC_FORM_TEXT_IMAGE_FOLDER + "\\" +
//to get Record ID
((MyAppPrincipal)System.Threading.Thread.CurrentPrincipal).Record.ID;
public string RootDirectory
{
get
{
return ROOT_DIRECTORY_FULL_PATH;
}
private set
{
}
}
private PathPermissions fullPermissions = PathPermissions.Read | PathPermissions.Upload;
private DirectoryItem[] GetSubDirectories(string path)
{
//we have only one directory no sub directories
//no need to go to file system to find that out
return new DirectoryItem[0];
}
private string GetDirectoryFullPath(string path)
{
return RootDirectory;
}
private FileItem[] GetFiles(string path)
{
string[] filesFullName = Directory.GetFiles(RootDirectory);
ArrayList files = new ArrayList();
for (int i = 0; i < filesFullName.Length; i++)
{
string fullPath = filesFullName[i];
System.IO.FileInfo currentFile = new System.IO.FileInfo(fullPath);
if (IsAlowedFileExtension(currentFile.Extension))
{
string url = string.Format("{0}?path={1}", HttpContext.Current.Request.ApplicationPath + "/app/FormTextImageHandler.ashx", currentFile.Name);
files.Add(new FileItem(
currentFile.Name, //file name
currentFile.Extension, //extension
currentFile.Length, //size
string.Empty,//currentFile.FullName, //location
url, //url
string.Empty,//tag
fullPermissions//permissions
));
}
}
return (FileItem[])files.ToArray(typeof(FileItem));
}
private bool IsAlowedFileExtension(string Extension)
{
if (Extension.Equals(".gif", StringComparison.InvariantCultureIgnoreCase))
return true;
if (Extension.Equals(".jpg", StringComparison.InvariantCultureIgnoreCase))
return true;
if (Extension.Equals(".png", StringComparison.InvariantCultureIgnoreCase))
return true;
return false;
}
public FolderContentProvider(HttpContext context, string[] searchPatterns, string[] viewPaths, string[] uploadPaths, string[] deletePaths, string selectedUrl, string selectedItemTag)
: base(context, searchPatterns, viewPaths, uploadPaths, deletePaths, selectedUrl, selectedItemTag)
{
}
public override string DeleteFile(string path)
{
//we do not allow removing files
return null;
}
public override string DeleteDirectory(string path)
{
//we don't have any sub directories
//and moreover we don't give delete rights
return null;
}
public override string StoreFile(Telerik.Web.UI.UploadedFile file, string path, string name, params string[] arguments)
{
int fileLength = (int)file.InputStream.Length;
byte[] content = new byte[fileLength];
file.InputStream.Read(content, 0, fileLength);
string fullPath = RootDirectory +"\\"+ name;
FileStream fileStream = new FileStream(fullPath, FileMode.OpenOrCreate);
fileStream.Write(content, 0, content.Length);
fileStream.Flush();
fileStream.Close();
return string.Empty;
}
public override DirectoryItem ResolveDirectory(string path)
{
DirectoryItem[] directories = new DirectoryItem[0];
FileItem[] files = this.GetFiles(RootDirectory);
DirectoryItem dir = new DirectoryItem("Images", string.Empty, RootDirectory, string.Empty, fullPermissions, files, directories);
return dir;
}
public override DirectoryItem ResolveRootDirectoryAsTree(string path)
{
//we don't have any subdirectories - everythinng is in the same folder
DirectoryItem[] directories = new DirectoryItem[0];
FileItem[] files = this.GetFiles(RootDirectory);
DirectoryItem root = new DirectoryItem("Images", string.Empty, "Images\\", string.Empty, fullPermissions, files, directories);
return root;
}
public override bool CanCreateDirectory
{
get
{
return false;
}
}
public override string CreateDirectory(string path, string name)
{
return null;
}
public override string StoreBitmap(Bitmap bitmap, string url, ImageFormat format)
{
return null;
}
public override Stream GetFile(string url)
{
return null;
}
public override string GetPath(string url)
{
return RootDirectory;
}
public override string GetFileName(string url)
{
return null;
}
[Obsolete]
public override DirectoryItem[] ResolveRootDirectoryAsList(string path)
{
return null;
}
public override bool CheckWritePermissions(string folderPath) {
return true;
}
}
Any idea's why the old version was able to insert into the field, but the new version is not?
The FileBrowserProvider API could be changed between the old and new versions. That why my suggestion is to examine the code of the following demo http://demos.telerik.com/aspnet-ajax/editor/examples/dbfilebrowsercontentprovider/defaultcs.aspx that works as expected and compare it with the code of your custom solution.
If you have any customized dialogs you may need to copy the EditorDialogs folder from the new installation that you are using and customize them from scratch.
Best regards,
Rumen

Trying to save comma-separated list

Trying to save selections from a CheckBoxList as a comma-separated list (string) in DB (one or more choices selected). I am using a proxy in order to save as a string because otherwise I'd have to create separate tables in the DB for a relation - the work is not worth it for this simple scenario and I was hoping that I could just convert it to a string and avoid that.
The CheckBoxList uses an enum for it's choices:
public enum Selection
{
Selection1,
Selection2,
Selection3
}
Not to be convoluted, but I use [Display(Name="Choice 1")] and an extension class to display something friendly on the UI. Not sure if I can save that string instead of just the enum, although I think if I save as enum it's not a big deal for me to "display" the friendly string on UI on some confirmation page.
This is the "Record" class that saves a string in the DB:
public virtual string MyCheckBox { get; set; }
This is the "Proxy", which is some sample I found but not directly dealing with enum, and which uses IEnumerable<string> (or should it be IEnumerable<Selection>?):
public IEnumerable<string> MyCheckBox
{
get
{
if (String.IsNullOrWhiteSpace(Record.MyCheckBox)) return new string[] { };
return Record
.MyCheckBox
.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries)
.Select(r => r.Trim())
.Where(r => !String.IsNullOrEmpty(r));
}
set
{
Record.MyCheckBox = value == null ? null : String.Join(",", value);
}
}
To save in the DB, I am trying to do this in a create class:
proxy.MyCheckBox = record.MyCheckBox; //getting error here
but am getting the error:
Cannot implicitly convert 'string' to System.Collections.Generic.IEnumerable'
I don't know, if it's possible or better, to use Parse or ToString from the API for enum values.
I know that doing something like this will store whatever I put in the ("") into the DB, so it's just a matter of figuring out how to overcome the error (or, if there is an alternative):
proxy.MyCheckBox = new[] {"foo", "bar"};
I am not good with this stuff and have just been digging and digging to come up with a solution. Any help is much appreciated.
You can accomplish this using a custom user type. The example below uses an ISet<string> on the class and stores the values as a delimited string.
[Serializable]
public class CommaDelimitedSet : IUserType
{
const string delimiter = ",";
#region IUserType Members
public new bool Equals(object x, object y)
{
if (ReferenceEquals(x, y))
{
return true;
}
var xSet = x as ISet<string>;
var ySet = y as ISet<string>;
if (xSet == null || ySet == null)
{
return false;
}
// compare set contents
return xSet.Except(ySet).Count() == 0 && ySet.Except(xSet).Count() == 0;
}
public int GetHashCode(object x)
{
return x.GetHashCode();
}
public object NullSafeGet(IDataReader rs, string[] names, object owner)
{
var outValue = NHibernateUtil.String.NullSafeGet(rs, names[0]) as string;
if (string.IsNullOrEmpty(outValue))
{
return new HashSet<string>();
}
else
{
var splitArray = outValue.Split(new[] {Delimiter}, StringSplitOptions.RemoveEmptyEntries);
return new HashSet<string>(splitArray);
}
}
public void NullSafeSet(IDbCommand cmd, object value, int index)
{
var inValue = value as ISet<string>;
object setValue = inValue == null ? null : string.Join(Delimiter, inValue);
NHibernateUtil.String.NullSafeSet(cmd, setValue, index);
}
public object DeepCopy(object value)
{
// return new ISet so that Equals can work
// see http://www.mail-archive.com/nhusers#googlegroups.com/msg11054.html
var set = value as ISet<string>;
if (set == null)
{
return null;
}
return new HashSet<string>(set);
}
public object Replace(object original, object target, object owner)
{
return original;
}
public object Assemble(object cached, object owner)
{
return DeepCopy(cached);
}
public object Disassemble(object value)
{
return DeepCopy(value);
}
public SqlType[] SqlTypes
{
get { return new[] {new SqlType(DbType.String)}; }
}
public Type ReturnedType
{
get { return typeof(ISet<string>); }
}
public bool IsMutable
{
get { return false; }
}
#endregion
}
Usage in mapping file:
Map(x => x.CheckboxValues.CustomType<CommaDelimitedSet>();

Creating Canonical URLs including an id and title slug

I want to replicate what StackOverflow does with its URLs.
For example:
Hidden Features of C#? - (Hidden Features of C#?)
or
Hidden Features of C#? - (Hidden Features of C#?)
Will Take you to the same page but when they return to the browser the first one is always returned.
How do you implement the change so the larger URL is returned?
The way that I've handled this before is to have two routes, registered in this order
routes.MapRoute(
null,
"questions/{id}/{title}",
new { controller = "Questions", action = "Index" },
new { id = #"\d+", title = #"[\w\-]*" });
routes.MapRoute(
null,
"questions/{id}",
new { controller = "Questions", action = "Index" },
new { id = #"\d+" });
now in the controller action,
public class QuestionsController
{
private readonly IQuestionRepository _questionRepo;
public QuestionsController(IQuestionRepository questionRepo)
{
_questionRepo = questionRepo;
}
public ActionResult Index(int id, string title)
{
var question = _questionRepo.Get(id);
if (string.IsNullOrWhiteSpace(title) || title != question.Title.ToSlug())
{
return RedirectToAction("Index", new { id, title = question.Title.ToSlug() }).AsMovedPermanently();
}
return View(question);
}
}
We'll permanently redirect to the URL that contains the title slug (lowercase title with hyphens as separators) if we only have the id. We also make sure that the title passed is the correct one by checking it against the slugged version of the question title, thereby creating a canonical URL for the question that contains both the id and the correct title slug.
A couple of the helpers used
public static class PermanentRedirectionExtensions
{
public static PermanentRedirectToRouteResult AsMovedPermanently
(this RedirectToRouteResult redirection)
{
return new PermanentRedirectToRouteResult(redirection);
}
}
public class PermanentRedirectToRouteResult : ActionResult
{
public RedirectToRouteResult Redirection { get; private set; }
public PermanentRedirectToRouteResult(RedirectToRouteResult redirection)
{
this.Redirection = redirection;
}
public override void ExecuteResult(ControllerContext context)
{
// After setting up a normal redirection, switch it to a 301
Redirection.ExecuteResult(context);
context.HttpContext.Response.StatusCode = 301;
context.HttpContext.Response.Status = "301 Moved Permanently";
}
}
public static class StringExtensions
{
private static readonly Encoding Encoding = Encoding.GetEncoding("Cyrillic");
public static string RemoveAccent(this string value)
{
byte[] bytes = Encoding.GetBytes(value);
return Encoding.ASCII.GetString(bytes);
}
public static string ToSlug(this string value)
{
if (string.IsNullOrWhiteSpace(value))
{
return string.Empty;
}
var str = value.RemoveAccent().ToLowerInvariant();
str = Regex.Replace(str, #"[^a-z0-9\s-]", "");
str = Regex.Replace(str, #"\s+", " ").Trim();
str = str.Substring(0, str.Length <= 200 ? str.Length : 200).Trim();
str = Regex.Replace(str, #"\s", "-");
str = Regex.Replace(str, #"-+", "-");
return str;
}
}

Resources