I want Website users to submit a file through a Web form. Sometimes multiple files. Then these files should be emailed to a specific set of users. I have created an Email Service that accepts a List attachments.
`
public class EmailModel
{
public EmailAddressModel FromUser { get; private set; }
public List<EmailAddressModel> ToAddresses { get; private set; }
public List<EmailAddressModel> CcAddresses { get; private set; }
public List<EmailAddressModel> BccAddresses { get; private set; }
public List<IFormFile> Attachments { get; private set; }
public string Subject { get; private set; }
public string Body { get; private set; }
public bool isHTML { get; private set; }
public EmailModel(EmailAddressModel fromUser, List<EmailAddressModel> toAddresses, List<EmailAddressModel> ccAddresses, List<EmailAddressModel> bccAddresses, List<IFormFile> attachments, string subject, string body, bool isHTML)
{
FromUser = fromUser;
ToAddresses = toAddresses;
CcAddresses = ccAddresses;
BccAddresses = bccAddresses;
Attachments = attachments;
Subject = subject;
Body = body;
this.isHTML = isHTML;
}
}
EmailService Prepare Email Attachments.
private void PrepareEmailAttachments()
{
foreach (IFormFile attachment in Email.Attachments)
{
if (attachment.Length > 0)
{
var stream = attachment.OpenReadStream();
using (MemoryStream mStream = new MemoryStream())
{
stream.CopyTo(mStream);
var data = mStream.ToArray();
Builder.Attachments.Add(attachment.FileName, data);
}
}
}
}
Test Code Snippet
List<IFormFile> Attachments = new List<IFormFile>();
using (var stream = File.OpenRead("TestFile\\Test Document.pdf"))
{
var file = new FormFile(stream, 0, stream.Length, null, Path.GetFileName(stream.Name))
{
Headers = new HeaderDictionary(),
ContentType = "application/pdf"
};
Attachments.Add(file);
}
EmailModel Email = new EmailModel(
From,
To,
CC,
BCC,
Attachments,
"Test Email",
"This is a build test",
true);
`
var stream = attachment.OpenReadStream() I am getting a "Cannot Access a closed stream."
`
Message:
Test method BA.Common.Tests.SendMail.SendEmail threw exception:
System.ObjectDisposedException: Cannot access a closed Stream.
Stack Trace:
BufferedFileStreamStrategy.Seek(Int64 offset, SeekOrigin origin)
FileStream.set_Position(Int64 value)
ReferenceReadStream.ctor(Stream inner, Int64 offset, Int64 length)
FormFile.OpenReadStream()
EmailService.PrepareEmailAttachments() line 111
EmailService.SendMail() line 43
SendMail.SendEmail() line 58
`
I am expecting that the attachment gets loaded into the MimeKit to be emailed. I have confirmed that the file is ready in attachment. But I cannot figure out why I can't open the attachment.
One of my good friends answered me via email after seeing my post, but he didn't answer here so I will. It turns out that the error was in my integration test, not my project.
I thought that once I added the file to the (Attachment.Add(file); that I could dispose of the stream. In my test I was closing my stream before loading my EmailModel. Simple fix, but I should have kept working up my stack trace to find the error. Thanks Andrew!
Related
In ASP.NET Core 6 Web API, I am using MailKit for Email configuration. I am using the SMTP server of my company which doesn't need a password.
I have this:
public class MailSettings
{
public string SmtpServer { get; set; }
public string SenderName { get; set; }
public string SenderEmail { get; set; }
public int SmtpPort { get; set; }
}
Since I am using my company SMTP configuration that needs no password, I use this method to send mails:
public async Task<bool> SendEmailAsync(MailRequest mailRequest)
{
var email = new MimeMessage { Sender = MailboxAddress.Parse(_mailSettings.SenderEmail) };
email.To.Add(MailboxAddress.Parse(mailRequest.ToEmail));
email.Subject = mailRequest.Subject;
var builder = new BodyBuilder();
if (mailRequest.Attachments != null)
{
foreach (var file in mailRequest.Attachments.Where(file => file.Length > 0))
{
byte[] fileBytes;
await using (var ms = new MemoryStream())
{
file.CopyTo(ms);
fileBytes = ms.ToArray();
}
builder.Attachments.Add((file.FileName + Guid.NewGuid().ToString()), fileBytes, ContentType.Parse(file.ContentType));
}
}
builder.HtmlBody = mailRequest.Body;
email.Body = builder.ToMessageBody();
try
{
using var smtp = new SmtpClient();
smtp.Connect(_mailSettings.SmtpServer, _mailSettings.SmtpPort, SecureSocketOptions.StartTls);
smtp.Authenticate(_mailSettings.SenderEmail);
await smtp.SendAsync(email);
smtp.Disconnect(true);
return true;
}
catch (Exception e)
{
_logger.Error(e, e.Source, e.InnerException, e.Message, e.ToString());
return false;
}
}
I got this error:
Argument 1: cannot convert from 'string' to 'MailKit.Security.SaslMechanism'
and it highlights this line of code:
smtp.Authenticate(_mailSettings.SenderEmail);
Expecting me to do it this way:
smtp.Authenticate(_mailSettings.SenderEmail, _mailSettings.Password);
How do I resolve this without password?
Thanks
I am trying to implement an IExternalSignatureContainer (itext7) to sign PDF with belgian id card.
There is twa smartcard versions to deal with:
RSA/SHA256 signature
ECDSA/SHA384
I achieved to sign the pdf with the RSA/SHA256 siganture but I am not able to make the same for the ECDSA/SHA384.
Here is the Sign method of the ExternalSignatureContainer:
public byte[] Sign(Stream data)
{
var signCert = this.chain.First();
IssuerAndSerialNumber issuerAndSerialNumber = new IssuerAndSerialNumber(signCert.IssuerDN, signCert.SerialNumber);
var signerGenerator = new SignerInfoGeneratorBuilder().Build(
new BeidSignatureFactory(
(data) => this.Sign(data), // Sign data with the eid-middleware
() => this.GetHashAlgorithm(), // Get the hash algorithm of the id card from the eid-middleware
() => this.GetEncryptionAlgorithm() // Get the encryption algorithm of the id card from the eid-middleware
), signCert);
CmsSignedDataGenerator gen = new CmsSignedDataGenerator();
gen.AddSignerInfoGenerator(signerGenerator);
IX509Store x509CertStore = X509StoreFactory.Create(
"Certificate/Collection",
new X509CollectionStoreParameters(this.chain));
gen.AddCertificates(x509CertStore);
CmsProcessableInputStream cmsProcessableInputStream = new CmsProcessableInputStream(data);
var sigData = gen.Generate(cmsProcessableInputStream, false);
return sigData.GetEncoded();
}
Here is a Custom ISignatureFactory and a custom ISigner that use the eid-middleware to access the begian eid card and use it to sign data.
internal class BeidSignatureFactory : ISignatureFactory
{
private readonly BeidSigner _signer = new BeidSigner();
private readonly BeidSigner.SignatureGenerator _signatureGenerator;
private readonly BeidSigner.AlgorithmGetter _getHashAlgorithm;
private readonly BeidSigner.AlgorithmGetter _getEncryptionAlgorithm;
public BeidSignatureFactory(BeidSigner.SignatureGenerator signatureGenerator, BeidSigner.AlgorithmGetter getHashAlgorithm, BeidSigner.AlgorithmGetter getEncryptionAlgorithm)
{
this._signatureGenerator = signatureGenerator;
this._getEncryptionAlgorithm = getEncryptionAlgorithm;
this._getHashAlgorithm = getHashAlgorithm;
}
public IStreamCalculator CreateCalculator()
{
var signer = new BeidSigner
{
Sign = this._signatureGenerator,
GetEncryptionAlgorithm = this._getEncryptionAlgorithm,
GetHashAlgorithm = this._getHashAlgorithm,
};
signer.Init(true, null);
return new DefaultSignatureCalculator(signer);
}
public object AlgorithmDetails
{
get
{
string encryptionAlgorithm = this._getEncryptionAlgorithm();
string hashAlgorithm = this._getHashAlgorithm();
switch (encryptionAlgorithm)
{
case "RSA":
return new AlgorithmIdentifier(PkcsObjectIdentifiers.Sha256WithRsaEncryption);
case "ECDSA":
return new AlgorithmIdentifier(X9ObjectIdentifiers.ECDsaWithSha384);
default:
return null;
}
}
}
}
internal class BeidSigner : ISigner
{
public delegate byte[] SignatureGenerator(byte[] data);
public delegate string AlgorithmGetter();
private byte[] _input;
public void Init(bool forSigning, ICipherParameters parameters)
{
this.Reset();
}
public void Update(byte input)
{
throw new NotImplementedException();
}
public void BlockUpdate(byte[] input, int inOff, int length)
{
this._input = input.Skip(inOff).Take(length).ToArray();
}
public byte[] GenerateSignature()
{
// This method doesn't exist, you will need to implement your own method here
return this.Sign(this._input);
}
public bool VerifySignature(byte[] signature)
{
throw new NotImplementedException();
}
public void Reset() { }
public string AlgorithmName
{
get
{
return $"{this.GetHashAlgorithm()}with{this.GetEncryptionAlgorithm()}";
}
}
public SignatureGenerator Sign { get; set; }
public AlgorithmGetter GetHashAlgorithm { get; set; }
public AlgorithmGetter GetEncryptionAlgorithm { get; set; }
}
Once the pdf signed with the EDCSA signature, the signature is not validated by acrobat reader "Document altered ro corrupted".
Perhaps I have completaly misunderstood the way tho sign the pdf.
Thanks for your help.
I have an Mvc project which is based on Asp.Net Core 5.0 . I have my own Core Layer and i have my own Photo,Video uploader method which is based my FileRepo class.
Here is my FileModel class:
public class FileModel
{
public int FileID { get; set; }
public string FileName { get; set; }
public string FileType { get; set; }
public string FileExtension { get; set; }
public string FileSlug { get; set; }
public string FilePath { get; set; }
public byte[] Data { get; set; }
}
Here is my File Uploader method :
public interface IFileUploader
{
Task<FileModel> FileUploadToDatabase(List<IFormFile> files);
Task<FileModel> FileUploadToPath(List<IFormFile> files);
Task<bool> FileDeleteFromPath(int id);
}
public class FileUploader : IFileUploader
{
private FileModel _fileModel;
public FileUploader(FileModel fileModel)
{
_fileModel = fileModel;
}
public async Task<FileModel> FileUploadToDatabase(List<IFormFile> files)
{
foreach (var file in files)
{
var fileName = Path.GetFileNameWithoutExtension(file.FileName);
var extension = Path.GetExtension(file.FileName);
_fileModel = new FileModel
{
FileName = fileName,
FileType = file.ContentType
};
using (var dataStream = new MemoryStream())
{
await file.CopyToAsync(dataStream);
_fileModel.Data = dataStream.ToArray();
}
}
return _fileModel;
}
public async Task<FileModel> FileUploadToPath(List<IFormFile> files)
{
foreach (var file in files)
{
var basePath = Path.Combine(Directory.GetCurrentDirectory() + "\\Files\\");
bool basePathExists = Directory.Exists(basePath);
if (!basePathExists) Directory.CreateDirectory(basePath);
var fileName = Path.GetFileNameWithoutExtension(file.FileName);
var filePath = Path.Combine(basePath, file.FileName);
var extension = Path.GetExtension(file.FileName);
if (!File.Exists(filePath))
{
using (var stream = new FileStream(filePath, FileMode.Create))
{
await file.CopyToAsync(stream);
}
_fileModel = new FileModel
{
FileName = fileName,
FileType = file.ContentType,
FilePath = filePath
};
}
}
return _fileModel;
}
}
As u guys can see,its a different layer and there is nothing related with my Mvc project.
Im getting error when i add those extension to my Mvc project.
The error says me that :
'Some services are not able to be constructed (Error while validating the service descriptor
'ServiceType: CORE_HBKSOFTWARE.Interfaces.IFileUploader Lifetime: Singleton ImplementationType:
CORE_HBKSOFTWARE.Classes.FileUploader': Unable to resolve service for type
'CORE_HBKSOFTWARE.Models.FileModel' while attempting to activate
'CORE_HBKSOFTWARE.Classes.FileUploader'.)'
I dont know why im getting that error. That FileModel class is seems to okey to me and i still dont know. I allready add my services.AddSingleton<IFileUploader, FileUploader>(); to my Startup.cs .
Thanks for any suggestion !
If you want to use constructor injection to create an instance of FileModel in your FileUploader class you need to register it to the IoC Container. You do that by calling following method in the ConfigureServices method:
services.AddSingleton<FileModel>();
You can also choose a different lifetime by calling AddScoped or AddTransient.
I have to send a request object via Refit which contains 2 IEnumerable and one string, but for some reason I can't send the object forward.
I've tried to use all the paramets from the interface. Ex: [Query(CollectionFormat.Csv)] or Multi / Pipes but no success.
I've also tried to create my own CustomUrlParameterFormatter but unfortunately here I'm stuck, because I don't see a good way to retrieve the name of the property from the object request that I'm sending.
The code for CustomUrlParameterFormatter
public class CustomUrlParameterFormatter : IUrlParameterFormatter
{
public string Format(object value, ParameterInfo parameterInfo)
{
if(value is IEnumerable enumerable)
{
var result = ToQueryString(enumerable, parameterInfo.Name);
return result;
}
return string.Empty;
}
public static string ToQueryString(IEnumerable query, string parameterName)
{
var values = query.Cast<object>().Select(ToString).ToArray();
var separator = parameterName + "=";
return values.Any() ? separator + string.Join("&" + separator, values) : "";
}
public static string ToString(object value)
{
var json = JsonConvert.SerializeObject(value).Replace("\\\"", "\"").Trim('"');
return Uri.EscapeUriString(json);
}
}
The Call from the IService that I'm using
[Get("/TestMethod")]
Task<HttpResponseMessage> TestMethod([Query]TestRequestDTO requestDTO, [Header("X-Correlation-ID")] string correlationId);
The Request object
public class TestRequestDTO
{
public IEnumerable<long> EnumOne { get; set; }
public IEnumerable<long> EnumTwo { get; set; }
public string MethodString { get; set; }
}
Also the RefitClient configuration
var refitSettings = new RefitSettings();
refitSettings.UrlParameterFormatter = new CustomUrlParameterFormatter();
services.AddRefitClient<IService>(refitSettings)
.ConfigureHttpClient(c => c.BaseAddress = new Uri(settings.Services.IService));
What I'm trying to achieve is something like
TestMethod?EnumOne =123&EnumOne =321&EnumTwo=123&EnumTwo=321&methodString=asdsaa
and instead I'm receiving other behavior
without CustomUrlParameterFormatter()
TestMethod?EnumOne=System.Collections.Generic.List`1%5BSystem.Int64%5D&EnumTwo=System.Collections.Generic.List`1%5BSystem.Int64%5D&MethodString=sdf
I created a ComplexType and am returning it in a service operation as shown here:
[WebGet]
public IQueryable<ComplexAddressType> GetCityByZip(string zip)
{
List<AddressType> normalizeAddress = NormalizeAddressProcess(new AddressType
{
ZIP = zip,
}).AddressList;
return normalizeAddress.Select(x =>new ComplexAddressType
{
ZIP = x.zip,
City = x.City,
State = x.State
}).AsQueryable();
}
When I try to invoke the service operation by calling http://localhost/MyService.svc/GetCityByZip?zip='20000', the service operation invocation works and the browser displays a list of cities.
When I try to invoke the service operation by calling http://localhost/MyService.svc/GetCityByZip?zip='20000'&$top=1, the browser displays an error page.
Could you can help me?
Assuming ComplexAddressType is actually a complex type, you cannot use the $top system query option with that service operation. If you enable verbose errors per the comment above, you are likely getting back this error:
Query options $orderby, $inlinecount, $skip and $top cannot be applied to the requested resource.
To be able to use $top with the service operation, you will need to return a collection of entity types rather than complex types.
You could also just introduce another parameter to your function call, so that you can use a URL such as the following:
http://localhost:59803/ScratchService.svc/GetProfiles?startsWith='ABC'&top=2
Sample code:
using System;
using System.Collections.Generic;
using System.Data.Entity;
using System.Data.Services;
using System.Data.Services.Common;
using System.Linq;
using System.ServiceModel;
using System.ServiceModel.Web;
namespace Scratch.Web
{
[ServiceBehavior(IncludeExceptionDetailInFaults = true)]
public class ScratchService : DataService<ScratchContext>
{
static ScratchService()
{
Database.SetInitializer(new ScratchContextInitializer());
}
public static void InitializeService(DataServiceConfiguration config)
{
config.SetEntitySetAccessRule("*", EntitySetRights.All);
config.SetServiceOperationAccessRule("*", ServiceOperationRights.AllRead);
config.DataServiceBehavior.MaxProtocolVersion = DataServiceProtocolVersion.V3;
config.UseVerboseErrors = true;
}
[WebGet]
public IQueryable<User> GetUsers(int numUsers)
{
var users = new List<User>();
for (int i = 0; i < numUsers; i++)
{
users.Add(new User
{
Id = i,
Password = i.ToString(),
Username = i.ToString()
});
}
return users.AsQueryable();
}
[WebGet]
public IQueryable<Profile> GetProfiles(string startsWith, int top)
{
var profiles = new List<Profile>
{
new Profile{ DisplayName = "A", Preferences = "1" },
new Profile{ DisplayName = "AB", Preferences = "2" },
new Profile{ DisplayName = "ABC", Preferences = "3" },
new Profile{ DisplayName = "ABCD", Preferences = "4" },
new Profile{ DisplayName = "ABCDE", Preferences = "5" },
new Profile{ DisplayName = "ABCDEF", Preferences = "6" },
new Profile{ DisplayName = "ABCDEFG", Preferences = "7" }
};
return profiles.Where(p => p.DisplayName.StartsWith(startsWith)).Take(top).AsQueryable();
}
}
public class ScratchContextInitializer : DropCreateDatabaseAlways<ScratchContext>
{
}
public class ScratchContext : DbContext
{
public DbSet<User> Users { get; set; }
}
public class Profile
{
public string DisplayName { get; set; }
public string Preferences { get; set; }
}
public class User
{
public int Id { get; set; }
public string Username { get; set; }
public string Password { get; set; }
public Profile Profile { get; set; }
}
}
Last code will work when GetCityByZip method has 2 parameters. First one for zip and second one for top. In your case you have parameters inconsistency and wcf can't find method.